337685 Work in progress on draft 6 websockets

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2831 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2011-02-28 09:45:41 +00:00
parent 8020110283
commit 2cc4fbb5ee
30 changed files with 1459 additions and 674 deletions

View File

@ -11,7 +11,7 @@ jetty-7.3.1-SNAPSHOT
+ 337270 Shared Timer for session management + 337270 Shared Timer for session management
+ 337271 Flush SSL endpoint when dispatch thread held forever + 337271 Flush SSL endpoint when dispatch thread held forever
+ 337678 Readded optional async connection mode for HttpClient + 337678 Readded optional async connection mode for HttpClient
+ 337685 Work in progress on draft 5 websockets + 337685 Work in progress on draft 6 websockets
+ 337746 Fixed Session deIdle recursion + 337746 Fixed Session deIdle recursion
+ 337784 Improve HashSessionManager for session migrations + 337784 Improve HashSessionManager for session migrations
+ 337878 Extra tests of security constraints + 337878 Extra tests of security constraints

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpSchemes; import org.eclipse.jetty.http.HttpSchemes;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.HttpVersions;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Buffers;
@ -44,10 +45,9 @@ import org.eclipse.jetty.util.thread.Timeout;
* *
* @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $ * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
*/ */
public class HttpConnection implements Connection public class HttpConnection extends AbstractConnection implements Connection
{ {
private HttpDestination _destination; private HttpDestination _destination;
private EndPoint _endp;
private HttpGenerator _generator; private HttpGenerator _generator;
private HttpParser _parser; private HttpParser _parser;
private boolean _http11 = true; private boolean _http11 = true;
@ -56,6 +56,7 @@ public class HttpConnection implements Connection
private Buffer _requestContentChunk; private Buffer _requestContentChunk;
private boolean _requestComplete; private boolean _requestComplete;
private boolean _reserved; private boolean _reserved;
// The current exchange waiting for a response // The current exchange waiting for a response
private volatile HttpExchange _exchange; private volatile HttpExchange _exchange;
private HttpExchange _pipeline; private HttpExchange _pipeline;
@ -64,6 +65,7 @@ public class HttpConnection implements Connection
public void dump() throws IOException public void dump() throws IOException
{ {
// TODO update to dumpable
Log.info("endp=" + _endp + " " + _endp.isBufferingInput() + " " + _endp.isBufferingOutput()); Log.info("endp=" + _endp + " " + _endp.isBufferingInput() + " " + _endp.isBufferingOutput());
Log.info("generator=" + _generator); Log.info("generator=" + _generator);
Log.info("parser=" + _parser.getState() + " " + _parser.isMoreInBuffer()); Log.info("parser=" + _parser.getState() + " " + _parser.isMoreInBuffer());
@ -74,16 +76,11 @@ public class HttpConnection implements Connection
HttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) HttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
{ {
_endp = endp; super(endp);
_generator = new HttpGenerator(requestBuffers,endp); _generator = new HttpGenerator(requestBuffers,endp);
_parser = new HttpParser(responseBuffers,endp,new Handler()); _parser = new HttpParser(responseBuffers,endp,new Handler());
} }
public long getTimeStamp()
{
return -1;
}
public void setReserved (boolean reserved) public void setReserved (boolean reserved)
{ {
_reserved = reserved; _reserved = reserved;
@ -442,11 +439,6 @@ public class HttpConnection implements Connection
{ {
} }
public EndPoint getEndPoint()
{
return _endp;
}
private void commitRequest() throws IOException private void commitRequest() throws IOException
{ {
synchronized (this) synchronized (this)

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
@ -312,7 +313,7 @@ public class HttpDestination
public void onNewConnection(final HttpConnection connection) throws IOException public void onNewConnection(final HttpConnection connection) throws IOException
{ {
HttpConnection q_connection = null; AbstractConnection q_connection = null;
synchronized (this) synchronized (this)
{ {

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.HttpVersions;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferCache.CachedBuffer; import org.eclipse.jetty.io.BufferCache.CachedBuffer;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
@ -690,9 +691,9 @@ public class HttpExchange
return this._connection != null; return this._connection != null;
} }
HttpConnection disassociate() AbstractConnection disassociate()
{ {
HttpConnection result = _connection; AbstractConnection result = _connection;
this._connection = null; this._connection = null;
if (getStatus() == STATUS_CANCELLING) if (getStatus() == STATUS_CANCELLING)
setStatus(STATUS_CANCELLED); setStatus(STATUS_CANCELLED);

View File

@ -30,6 +30,7 @@ import junit.framework.TestCase;
import org.eclipse.jetty.client.security.ProxyAuthorization; import org.eclipse.jetty.client.security.ProxyAuthorization;
import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.EofException;
@ -473,7 +474,7 @@ public class HttpExchangeTest extends TestCase
//try to get a connection, and only wait 500ms, as we have //try to get a connection, and only wait 500ms, as we have
//already reserved the max, should return null //already reserved the max, should return null
org.eclipse.jetty.client.HttpConnection c = destination.reserveConnection(500); AbstractConnection c = destination.reserveConnection(500);
assertNull(c); assertNull(c);
//unreserve first connection //unreserve first connection

View File

@ -636,6 +636,7 @@ public abstract class AbstractBuffer implements Buffer
if (bytes!=null) if (bytes!=null)
return new String(bytes,getIndex(),length(),charset); return new String(bytes,getIndex(),length(),charset);
return new String(asArray(), 0, length(),charset); return new String(asArray(), 0, length(),charset);
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -0,0 +1,51 @@
package org.eclipse.jetty.io;
import java.io.IOException;
import org.eclipse.jetty.util.log.Log;
public abstract class AbstractConnection implements Connection
{
private final long _timeStamp;
protected final EndPoint _endp;
public AbstractConnection(EndPoint endp)
{
_endp=endp;
_timeStamp = System.currentTimeMillis();
}
public AbstractConnection(EndPoint endp,long timestamp)
{
_endp=endp;
_timeStamp = timestamp;
}
public long getTimeStamp()
{
return _timeStamp;
}
public EndPoint getEndPoint()
{
return _endp;
}
public void idleExpired()
{
try
{
_endp.close();
}
catch(IOException e)
{
Log.ignore(e);
}
}
public String toString()
{
return super.toString()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
}
}

View File

@ -38,10 +38,21 @@ public interface Connection
*/ */
Connection handle() throws IOException; Connection handle() throws IOException;
/**
* @return the timestamp at which the connection was created
*/
long getTimeStamp(); long getTimeStamp();
boolean isIdle(); boolean isIdle();
boolean isSuspended(); boolean isSuspended();
/**
* Called when the connection idle timeout expires
*/
void idleExpired();
/**
* Called when the connection is closed
*/
void closed(); void closed();
} }

View File

@ -248,14 +248,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
protected void idleExpired() protected void idleExpired()
{ {
try _connection.idleExpired();
{
close();
}
catch (IOException e)
{
Log.ignore(e);
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.HttpVersions;
import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.Parser; import org.eclipse.jetty.http.Parser;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferCache.CachedBuffer; import org.eclipse.jetty.io.BufferCache.CachedBuffer;
@ -86,17 +87,15 @@ import org.eclipse.jetty.util.thread.Timeout;
* </p> * </p>
* *
*/ */
public class HttpConnection implements Connection public class HttpConnection extends AbstractConnection implements Connection
{ {
private static final int UNKNOWN = -2; private static final int UNKNOWN = -2;
private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<HttpConnection>(); private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<HttpConnection>();
private final long _timeStamp=System.currentTimeMillis();
private int _requests; private int _requests;
private volatile boolean _handling; private volatile boolean _handling;
protected final Connector _connector; protected final Connector _connector;
protected final EndPoint _endp;
protected final Server _server; protected final Server _server;
protected final HttpURI _uri; protected final HttpURI _uri;
@ -143,9 +142,9 @@ public class HttpConnection implements Connection
*/ */
public HttpConnection(Connector connector, EndPoint endpoint, Server server) public HttpConnection(Connector connector, EndPoint endpoint, Server server)
{ {
super(endpoint);
_uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
_connector = connector; _connector = connector;
_endp = endpoint;
HttpBuffers ab = (HttpBuffers)_connector; HttpBuffers ab = (HttpBuffers)_connector;
_parser = new HttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); _parser = new HttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler());
_requestFields = new HttpFields(); _requestFields = new HttpFields();
@ -161,9 +160,9 @@ public class HttpConnection implements Connection
protected HttpConnection(Connector connector, EndPoint endpoint, Server server, protected HttpConnection(Connector connector, EndPoint endpoint, Server server,
Parser parser, Generator generator, Request request) Parser parser, Generator generator, Request request)
{ {
super(endpoint);
_uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); _uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
_connector = connector; _connector = connector;
_endp = endpoint;
_parser = parser; _parser = parser;
_requestFields = new HttpFields(); _requestFields = new HttpFields();
_responseFields = new HttpFields(server.getMaxCookieVersion()); _responseFields = new HttpFields(server.getMaxCookieVersion());
@ -198,15 +197,6 @@ public class HttpConnection implements Connection
return _server; return _server;
} }
/* ------------------------------------------------------------ */
/**
* @return The time this connection was established.
*/
public long getTimeStamp()
{
return _timeStamp;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @return Returns the associatedObject. * @return Returns the associatedObject.
@ -278,15 +268,6 @@ public class HttpConnection implements Connection
return false; return false;
} }
/* ------------------------------------------------------------ */
/**
* @return The {@link EndPoint} for this connection.
*/
public EndPoint getEndPoint()
{
return _endp;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @return <code>false</code> (this method is not yet implemented) * @return <code>false</code> (this method is not yet implemented)

View File

@ -15,6 +15,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
@ -279,10 +280,9 @@ public class ConnectHandler extends HandlerWrapper
private ClientToProxyConnection prepareConnections(ConcurrentMap<String, Object> context, SocketChannel channel, Buffer buffer) private ClientToProxyConnection prepareConnections(ConcurrentMap<String, Object> context, SocketChannel channel, Buffer buffer)
{ {
HttpConnection httpConnection = HttpConnection.getCurrentConnection(); HttpConnection httpConnection = HttpConnection.getCurrentConnection();
ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer);
ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp()); ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp());
ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer,clientToProxy);
clientToProxy.setConnection(proxyToServer); clientToProxy.setConnection(proxyToServer);
proxyToServer.setConnection(clientToProxy);
return clientToProxy; return clientToProxy;
} }
@ -307,9 +307,9 @@ public class ConnectHandler extends HandlerWrapper
return new ClientToProxyConnection(context, channel, endPoint, timeStamp); return new ClientToProxyConnection(context, channel, endPoint, timeStamp);
} }
protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer buffer) protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer buffer,ClientToProxyConnection toProxy)
{ {
return new ProxyToServerConnection(context, buffer); return new ProxyToServerConnection(context, buffer,toProxy);
} }
private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException
@ -413,7 +413,7 @@ public class ConnectHandler extends HandlerWrapper
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey selectionKey) throws IOException protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey selectionKey) throws IOException
{ {
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, selectionKey); SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, selectionKey);
// TODO endp.setMaxIdleTime(_writeTimeout); endp.setMaxIdleTime(_writeTimeout);
return endp; return endp;
} }
@ -421,8 +421,7 @@ public class ConnectHandler extends HandlerWrapper
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
{ {
ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment(); ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment();
proxyToServer.setTimeStamp(System.currentTimeMillis()); proxyToServer = new ProxyToServerConnection(endpoint,proxyToServer);
proxyToServer.setEndPoint(endpoint);
return proxyToServer; return proxyToServer;
} }
@ -450,28 +449,43 @@ public class ConnectHandler extends HandlerWrapper
} }
} }
public class ProxyToServerConnection implements Connection public class ProxyToServerConnection extends AbstractConnection implements Connection
{ {
private final CountDownLatch _ready = new CountDownLatch(1); private final CountDownLatch _ready ;
private final Buffer _buffer = new IndirectNIOBuffer(1024); private final Buffer _buffer;
private final ConcurrentMap<String, Object> _context; private final ConcurrentMap<String, Object> _context;
private final ClientToProxyConnection _toClient;
private volatile Buffer _data; private volatile Buffer _data;
private volatile ClientToProxyConnection _toClient;
private volatile long _timestamp;
private volatile SelectChannelEndPoint _endPoint;
public ProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer data) public ProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer data, ClientToProxyConnection toClient)
{ {
super(null);
_context = context; _context = context;
_data = data; _data = data;
_ready = new CountDownLatch(1);
_buffer = new IndirectNIOBuffer(1024);
_toClient=toClient;
}
public ProxyToServerConnection(EndPoint endp,ProxyToServerConnection connection)
{
super(endp);
_ready=connection._ready;
_buffer=connection._buffer;
_context=connection._context;
_data=connection._data;
_toClient=connection._toClient;
if (_toClient!=null)
_toClient.setConnection(this);
} }
@Override @Override
public String toString() public String toString()
{ {
StringBuilder builder = new StringBuilder("ProxyToServer"); StringBuilder builder = new StringBuilder("ProxyToServer");
builder.append("(:").append(_endPoint.getLocalPort()); builder.append("(:").append(_endp.getLocalPort());
builder.append("<=>:").append(_endPoint.getRemotePort()); builder.append("<=>:").append(_endp.getRemotePort());
return builder.append(")").toString(); return builder.append(")").toString();
} }
@ -482,18 +496,18 @@ public class ConnectHandler extends HandlerWrapper
{ {
if (_data != null) if (_data != null)
{ {
int written = write(_endPoint, _data, _context); int written = write(_endp, _data, _context);
_logger.debug("{}: written to server {} bytes", this, written); _logger.debug("{}: written to server {} bytes", this, written);
_data = null; _data = null;
} }
while (true) while (true)
{ {
int read = read(_endPoint, _buffer, _context); int read = read(_endp, _buffer, _context);
if (read == -1) if (read == -1)
{ {
_logger.debug("{}: server closed connection {}", this, _endPoint); _logger.debug("{}: server closed connection {}", this, _endp);
close(); close();
break; break;
} }
@ -501,8 +515,8 @@ public class ConnectHandler extends HandlerWrapper
if (read == 0) if (read == 0)
break; break;
_logger.debug("{}: read from server {} bytes {}", this, read, _endPoint); _logger.debug("{}: read from server {} bytes {}", this, read, _endp);
int written = write(_toClient._endPoint, _buffer, _context); int written = write(_toClient.getEndPoint(), _buffer, _context);
_logger.debug("{}: written to {} {} bytes", this, _toClient, written); _logger.debug("{}: written to {} {} bytes", this, _toClient, written);
} }
return this; return this;
@ -530,26 +544,6 @@ public class ConnectHandler extends HandlerWrapper
} }
} }
public void setConnection(ClientToProxyConnection connection)
{
_toClient = connection;
}
public long getTimeStamp()
{
return _timestamp;
}
public void setTimeStamp(long timestamp)
{
_timestamp = timestamp;
}
public void setEndPoint(SelectChannelEndPoint endpoint)
{
_endPoint = endpoint;
}
public boolean isIdle() public boolean isIdle()
{ {
return false; return false;
@ -588,7 +582,7 @@ public class ConnectHandler extends HandlerWrapper
public void closeServer() throws IOException public void closeServer() throws IOException
{ {
_endPoint.close(); _endp.close();
} }
public void close() public void close()
@ -613,30 +607,27 @@ public class ConnectHandler extends HandlerWrapper
} }
} }
public class ClientToProxyConnection implements Connection public class ClientToProxyConnection extends AbstractConnection implements Connection
{ {
private final Buffer _buffer = new IndirectNIOBuffer(1024); private final Buffer _buffer = new IndirectNIOBuffer(1024);
private final ConcurrentMap<String, Object> _context; private final ConcurrentMap<String, Object> _context;
private final SocketChannel _channel; private final SocketChannel _channel;
private final EndPoint _endPoint;
private final long _timestamp;
private volatile ProxyToServerConnection _toServer; private volatile ProxyToServerConnection _toServer;
private boolean _firstTime = true; private boolean _firstTime = true;
public ClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timestamp) public ClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timestamp)
{ {
super(endPoint,timestamp);
_context = context; _context = context;
_channel = channel; _channel = channel;
_endPoint = endPoint;
_timestamp = timestamp;
} }
@Override @Override
public String toString() public String toString()
{ {
StringBuilder builder = new StringBuilder("ClientToProxy"); StringBuilder builder = new StringBuilder("ClientToProxy");
builder.append("(:").append(_endPoint.getLocalPort()); builder.append("(:").append(_endp.getLocalPort());
builder.append("<=>:").append(_endPoint.getRemotePort()); builder.append("<=>:").append(_endp.getRemotePort());
return builder.append(")").toString(); return builder.append(")").toString();
} }
@ -654,11 +645,11 @@ public class ConnectHandler extends HandlerWrapper
while (true) while (true)
{ {
int read = read(_endPoint, _buffer, _context); int read = read(_endp, _buffer, _context);
if (read == -1) if (read == -1)
{ {
_logger.debug("{}: client closed connection {}", this, _endPoint); _logger.debug("{}: client closed connection {}", this, _endp);
close(); close();
break; break;
} }
@ -666,8 +657,8 @@ public class ConnectHandler extends HandlerWrapper
if (read == 0) if (read == 0)
break; break;
_logger.debug("{}: read from client {} bytes {}", this, read, _endPoint); _logger.debug("{}: read from client {} bytes {}", this, read, _endp);
int written = write(_toServer._endPoint, _buffer, _context); int written = write(_toServer.getEndPoint(), _buffer, _context);
_logger.debug("{}: written to {} {} bytes", this, _toServer, written); _logger.debug("{}: written to {} {} bytes", this, _toServer, written);
} }
return this; return this;
@ -696,11 +687,6 @@ public class ConnectHandler extends HandlerWrapper
} }
} }
public long getTimeStamp()
{
return _timestamp;
}
public boolean isIdle() public boolean isIdle()
{ {
return false; return false;
@ -722,7 +708,7 @@ public class ConnectHandler extends HandlerWrapper
public void closeClient() throws IOException public void closeClient() throws IOException
{ {
_endPoint.close(); _endp.close();
} }
public void closeServer() throws IOException public void closeServer() throws IOException

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
@ -39,6 +40,14 @@ public class StringUtil
public final static String __UTF8Alt="UTF8"; public final static String __UTF8Alt="UTF8";
public final static String __UTF16="UTF-16"; public final static String __UTF16="UTF-16";
public final static Charset __UTF8_CHARSET;
public final static Charset __ISO_8859_1_CHARSET;
static
{
__UTF8_CHARSET=Charset.forName(__UTF8);
__ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
}
private static char[] lowercases = { private static char[] lowercases = {
'\000','\001','\002','\003','\004','\005','\006','\007', '\000','\001','\002','\003','\004','\005','\006','\007',

View File

@ -1,94 +0,0 @@
// ========================================================================
// 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 org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
final class FrameHandlerD5 implements WebSocketParser.FrameHandler
{
public final static byte PING=1;
public final static byte PONG=1;
final WebSocketConnectionD05 _connection;
final WebSocket _websocket;
final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
boolean _fragmented=false;
FrameHandlerD5(WebSocketConnectionD05 connection, WebSocket websocket)
{
_connection=connection;
_websocket=websocket;
}
public void onFrame(boolean more, byte flags, byte opcode, Buffer buffer)
{
try
{
byte[] array=buffer.array();
if (opcode==0)
{
if (more)
{
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
_fragmented=true;
}
else if (_fragmented)
{
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
_websocket.onMessage(opcode,_utf8.toString());
_utf8.reset();
_fragmented=false;
}
else
{
_websocket.onMessage(opcode,buffer.toString("utf-8"));
}
}
else if (opcode==PING)
{
_connection.sendMessage(PONG,buffer.array(),buffer.getIndex(),buffer.length());
}
else if (opcode==PONG)
{
}
else
{
if (more)
{
_websocket.onFragment(true,opcode,array,buffer.getIndex(),buffer.length());
}
else if (_fragmented)
{
_websocket.onFragment(false,opcode,array,buffer.getIndex(),buffer.length());
}
else
{
_websocket.onMessage(opcode,array,buffer.getIndex(),buffer.length());
}
}
}
catch(ThreadDeath th)
{
throw th;
}
catch(Throwable th)
{
Log.warn(th);
}
}
}

View File

@ -17,13 +17,29 @@ import java.io.IOException;
public interface WebSocket public interface WebSocket
{ {
@Deprecated
public final byte LENGTH_FRAME=(byte)0x80; public final byte LENGTH_FRAME=(byte)0x80;
@Deprecated
public final byte SENTINEL_FRAME=(byte)0x00; public final byte SENTINEL_FRAME=(byte)0x00;
public final static byte OP_CONTINUATION = 0x00;
public final static byte OP_CLOSE = 0x01;
public final static byte OP_PING = 0x02;
public final static byte OP_PONG = 0x03;
public final static byte OP_TEXT = 0x04;
public final static byte OP_BINARY = 0x05;
public final static int CLOSE_NORMAL=1000;
public final static int CLOSE_SHUTDOWN=1001;
public final static int CLOSE_PROTOCOL=1002;
public final static int CLOSE_DATA=1003;
public final static int CLOSE_LARGE=1004;
void onConnect(Outbound outbound); void onConnect(Outbound outbound);
void onMessage(byte opcode,String data); void onMessage(byte opcode,String data);
void onFragment(boolean more,byte opcode,byte[] data, int offset, int length); void onFragment(boolean more,byte opcode,byte[] data, int offset, int length);
void onMessage(byte opcode,byte[] data, int offset, int length); void onMessage(byte opcode,byte[] data, int offset, int length);
void onDisconnect(); void onDisconnect(); // TODO add code
public interface Outbound public interface Outbound
{ {
@ -32,6 +48,7 @@ public interface WebSocket
void sendMessage(byte opcode,byte[] data, int offset, int length) throws IOException; void sendMessage(byte opcode,byte[] data, int offset, int length) throws IOException;
void sendFragment(boolean more,byte opcode,byte[] data, int offset, int length) throws IOException; void sendFragment(boolean more,byte opcode,byte[] data, int offset, int length) throws IOException;
void disconnect(); void disconnect();
void disconnect(int code,String message);
boolean isOpen(); boolean isOpen();
} }
} }

View File

@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Connection;
public interface WebSocketConnection extends Connection, WebSocket.Outbound public interface WebSocketConnection extends Connection
{ {
void fillBuffersFrom(Buffer buffer); void fillBuffersFrom(Buffer buffer);

View File

@ -20,6 +20,7 @@ import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayBuffer;
@ -29,13 +30,11 @@ import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
public class WebSocketConnectionD00 implements WebSocketConnection public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.Outbound
{ {
final IdleCheck _idle; final IdleCheck _idle;
final EndPoint _endp;
final WebSocketParser _parser; final WebSocketParser _parser;
final WebSocketGenerator _generator; final WebSocketGenerator _generator;
final long _timestamp;
final WebSocket _websocket; final WebSocket _websocket;
String _key1; String _key1;
String _key2; String _key2;
@ -50,14 +49,13 @@ public class WebSocketConnectionD00 implements WebSocketConnection
public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft) public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
throws IOException throws IOException
{ {
super(endpoint,timestamp);
// TODO - can we use the endpoint idle mechanism? // TODO - can we use the endpoint idle mechanism?
if (endpoint instanceof AsyncEndPoint) if (endpoint instanceof AsyncEndPoint)
((AsyncEndPoint)endpoint).cancelIdle(); ((AsyncEndPoint)endpoint).cancelIdle();
_endp = endpoint;
_endp.setMaxIdleTime(maxIdleTime); _endp.setMaxIdleTime(maxIdleTime);
_timestamp = timestamp;
_websocket = websocket; _websocket = websocket;
// Select the parser/generators to use // Select the parser/generators to use
@ -220,11 +218,6 @@ public class WebSocketConnectionD00 implements WebSocketConnection
_websocket.onDisconnect(); _websocket.onDisconnect();
} }
public long getTimeStamp()
{
return _timestamp;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(java.lang.String) * @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(java.lang.String)
@ -264,12 +257,17 @@ public class WebSocketConnectionD00 implements WebSocketConnection
*/ */
public void sendFragment(boolean more,byte opcode, byte[] content, int offset, int length) throws IOException public void sendFragment(boolean more,byte opcode, byte[] content, int offset, int length) throws IOException
{ {
_generator.addFragment(more,opcode,content,offset,length,_endp.getMaxIdleTime()); _generator.addFragment(!more,opcode,content,offset,length,_endp.getMaxIdleTime());
_generator.flush(); _generator.flush();
checkWriteable(); checkWriteable();
_idle.access(_endp); _idle.access(_endp);
} }
public void disconnect(int code, String message)
{
throw new UnsupportedOperationException();
}
public void disconnect() public void disconnect()
{ {
try try
@ -380,4 +378,5 @@ public class WebSocketConnectionD00 implements WebSocketConnection
_websocket.onConnect(this); _websocket.onConnect(this);
} }
} }
} }

View File

@ -1,373 +0,0 @@
// ========================================================================
// 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.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.util.log.Log;
public class WebSocketConnectionD05 implements WebSocketConnection
{
final IdleCheck _idle;
final EndPoint _endp;
final WebSocketParser _parser;
final WebSocketGenerator _generator;
final long _timestamp;
final WebSocket _websocket;
String _key1;
String _key2;
ByteArrayBuffer _hixieBytes;
public WebSocketConnectionD05(WebSocket websocket, EndPoint endpoint,int draft)
throws IOException
{
this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
}
public WebSocketConnectionD05(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
throws IOException
{
// TODO - can we use the endpoint idle mechanism?
if (endpoint instanceof AsyncEndPoint)
((AsyncEndPoint)endpoint).cancelIdle();
_endp = endpoint;
_endp.setMaxIdleTime(maxIdleTime);
_timestamp = timestamp;
_websocket = websocket;
_generator = new WebSocketGeneratorD05(buffers, _endp);
_parser = new WebSocketParserD05(buffers, endpoint, new FrameHandlerD5(this,_websocket),true);
// 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 void setHixieKeys(String key1,String key2)
{
_key1=key1;
_key2=key2;
_hixieBytes=new IndirectNIOBuffer(16);
}
public Connection handle() throws IOException
{
try
{
// handle stupid hixie random bytes
if (_hixieBytes!=null)
{
// take any available bytes from the parser buffer, which may have already been read
Buffer buffer=_parser.getBuffer();
if (buffer!=null && buffer.length()>0)
{
int l=buffer.length();
if (l>(8-_hixieBytes.length()))
l=8-_hixieBytes.length();
_hixieBytes.put(buffer.peek(buffer.getIndex(),l));
buffer.skip(l);
}
// while we are not blocked
while(_endp.isOpen())
{
// do we now have enough
if (_hixieBytes.length()==8)
{
// we have the silly random bytes
// so let's work out the stupid 16 byte reply.
doTheHixieHixieShake();
_endp.flush(_hixieBytes);
_hixieBytes=null;
_endp.flush();
break;
}
// no, then let's fill
int filled=_endp.fill(_hixieBytes);
if (filled<0)
{
_endp.close();
break;
}
}
_websocket.onConnect(this);
return this;
}
// handle the framing protocol
boolean progress=true;
while (progress)
{
int flushed=_generator.flush();
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
{
if (_endp.isOpen())
{
_idle.access(_endp);
checkWriteable();
}
}
return this;
}
private void doTheHixieHixieShake()
{
byte[] result=WebSocketConnectionD05.doTheHixieHixieShake(
WebSocketConnectionD05.hixieCrypt(_key1),
WebSocketConnectionD05.hixieCrypt(_key2),
_hixieBytes.asArray());
_hixieBytes.clear();
_hixieBytes.put(result);
}
public boolean isOpen()
{
return _endp!=null&&_endp.isOpen();
}
public boolean isIdle()
{
return _parser.isBufferEmpty() && _generator.isBufferEmpty();
}
public boolean isSuspended()
{
return false;
}
public void closed()
{
_websocket.onDisconnect();
}
public long getTimeStamp()
{
return _timestamp;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(java.lang.String)
*/
public void sendMessage(String content) throws IOException
{
sendMessage(WebSocket.SENTINEL_FRAME,content);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(byte, java.lang.String)
*/
public void sendMessage(byte frame, String content) throws IOException
{
_generator.addFrame(frame,content,_endp.getMaxIdleTime());
_generator.flush();
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(byte, byte[], int, int)
*/
public void sendMessage(byte opcode, byte[] content, int offset, int length) throws IOException
{
_generator.addFrame(opcode,content,offset,length,_endp.getMaxIdleTime());
_generator.flush();
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendFragment(boolean, byte, byte[], int, int)
*/
public void sendFragment(boolean more,byte opcode, byte[] content, int offset, int length) throws IOException
{
_generator.addFragment(more,opcode,content,offset,length,_endp.getMaxIdleTime());
_generator.flush();
checkWriteable();
_idle.access(_endp);
}
public void disconnect()
{
try
{
_generator.flush(_endp.getMaxIdleTime());
_endp.close();
}
catch(IOException e)
{
Log.ignore(e);
}
}
public void fillBuffersFrom(Buffer buffer)
{
_parser.fill(buffer);
}
private void checkWriteable()
{
if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
((AsyncEndPoint)_endp).scheduleWrite();
}
/* ------------------------------------------------------------ */
static long hixieCrypt(String key)
{
// Don't ask me what all this is about.
// I think it's pretend secret stuff, kind of
// like talking in pig latin!
long number=0;
int spaces=0;
for (char c : key.toCharArray())
{
if (Character.isDigit(c))
number=number*10+(c-'0');
else if (c==' ')
spaces++;
}
return number/spaces;
}
public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte [] fodder = new byte[16];
fodder[0]=(byte)(0xff&(key1>>24));
fodder[1]=(byte)(0xff&(key1>>16));
fodder[2]=(byte)(0xff&(key1>>8));
fodder[3]=(byte)(0xff&key1);
fodder[4]=(byte)(0xff&(key2>>24));
fodder[5]=(byte)(0xff&(key2>>16));
fodder[6]=(byte)(0xff&(key2>>8));
fodder[7]=(byte)(0xff&key2);
for (int i=0;i<8;i++)
fodder[8+i]=key3[i];
md.update(fodder);
byte[] result=md.digest();
return result;
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
}
private interface IdleCheck
{
void access(EndPoint endp);
}
public void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException
{
String uri=request.getRequestURI();
String query=request.getQueryString();
if (query!=null && query.length()>0)
uri+="?"+query;
String host=request.getHeader("Host");
String key1 = request.getHeader("Sec-WebSocket-Key1");
if (key1!=null)
{
String key2 = request.getHeader("Sec-WebSocket-Key2");
setHixieKeys(key1,key2);
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("Sec-WebSocket-Origin",origin);
response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
if (subprotocol!=null)
response.addHeader("Sec-WebSocket-Protocol",subprotocol);
response.sendError(101,"WebSocket Protocol Handshake");
}
else
{
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("WebSocket-Origin",origin);
response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
if (subprotocol!=null)
response.addHeader("WebSocket-Protocol",subprotocol);
response.sendError(101,"Web Socket Protocol Handshake");
response.flushBuffer();
_websocket.onConnect(this);
}
}
}

View File

@ -0,0 +1,481 @@
// ========================================================================
// 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.security.MessageDigest;
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.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;
public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
{
private final static byte[] MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1_CHARSET);
private final static byte[] NORMAL_CLOSE=new byte[] { 1000/0xff, (byte)(1000%0xff) };
private final IdleCheck _idle;
private final WebSocketParser _parser;
private final WebSocketGenerator _generator;
private final WebSocket _websocket;
private boolean _closedIn;
private boolean _closedOut;
private final WebSocketParser.FrameHandler _frameHandler= new WebSocketParser.FrameHandler()
{
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
private byte _opcode=-1;
public void onFrame(boolean more, byte flags, byte opcode, Buffer buffer)
{
synchronized(WebSocketConnectionD06.this)
{
// Ignore incoming after a close
if (_closedIn)
return;
try
{
byte[] array=buffer.array();
switch(opcode)
{
case WebSocket.OP_CONTINUATION:
{
// If text, append to the message buffer
if (_opcode==WebSocket.OP_TEXT)
{
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
// If this is the last fragment, deliver the text buffer
if (!more)
{
String msg =_utf8.toString();
_utf8.reset();
_opcode=-1;
_websocket.onMessage(WebSocket.OP_TEXT,msg);
}
}
else
{
// deliver the non-text fragment
if (!more)
_opcode=-1;
_websocket.onFragment(more,_opcode,array,buffer.getIndex(),buffer.length());
}
break;
}
case WebSocket.OP_TEXT:
{
if (more)
{
// If this is a text fragment, append to buffer
_opcode=WebSocket.OP_TEXT;
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
}
else
{
// Deliver the message
_websocket.onMessage(opcode,buffer.toString(StringUtil.__UTF8));
}
break;
}
case WebSocket.OP_PING:
{
Log.debug("PING {}",this);
if (!_closedOut)
getOutbound().sendMessage(WebSocket.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
break;
}
case WebSocket.OP_PONG:
{
Log.debug("PONG {}",this);
break;
}
case WebSocket.OP_CLOSE:
{
int code=-1;
String message=null;
if (buffer.length()>=2)
{
code=buffer.array()[buffer.getIndex()]*0xff+buffer.array()[buffer.getIndex()+1];
if (buffer.length()>2)
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8_CHARSET);
}
closeIn(code,message);
break;
}
default:
{
if (more)
{
_opcode=opcode;
_websocket.onFragment(more,opcode,array,buffer.getIndex(),buffer.length());
}
else
_websocket.onMessage(opcode,array,buffer.getIndex(),buffer.length());
}
}
}
catch(ThreadDeath th)
{
throw th;
}
catch(Throwable th)
{
Log.warn(th);
}
}
}
public String toString()
{
return WebSocketConnectionD06.this.toString()+"FH";
}
};
private final WebSocket.Outbound _outbound = new WebSocket.Outbound()
{
volatile boolean _disconnecting;
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(java.lang.String)
*/
public void sendMessage(String content) throws IOException
{
sendMessage(WebSocket.OP_TEXT,content);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(byte, java.lang.String)
*/
public synchronized void sendMessage(byte opcode, String content) throws IOException
{
if (_closedOut)
throw new IOException("closing");
_generator.addFrame(opcode,content,_endp.getMaxIdleTime());
_generator.flush();
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendMessage(byte, byte[], int, int)
*/
public synchronized void sendMessage(byte opcode, byte[] content, int offset, int length) throws IOException
{
if (_closedOut)
throw new IOException("closing");
_generator.addFrame(opcode,content,offset,length,_endp.getMaxIdleTime());
_generator.flush();
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.websocket.WebSocketConnection#sendFragment(boolean, byte, byte[], int, int)
*/
public void sendFragment(boolean more,byte opcode, byte[] content, int offset, int length) throws IOException
{
if (_closedOut)
throw new IOException("closing");
_generator.addFragment(!more,opcode,content,offset,length,_endp.getMaxIdleTime());
_generator.flush();
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
public boolean isOpen()
{
return _endp!=null&&_endp.isOpen();
}
/* ------------------------------------------------------------ */
public void disconnect(int code, String message)
{
if (_disconnecting)
return;
_disconnecting=true;
WebSocketConnectionD06.this.closeOut(code,message);
}
/* ------------------------------------------------------------ */
public void disconnect()
{
if (_disconnecting)
return;
_disconnecting=true;
WebSocketConnectionD06.this.closeOut(1000,null);
}
};
/* ------------------------------------------------------------ */
public WebSocketConnectionD06(WebSocket websocket, EndPoint endpoint,int draft)
throws IOException
{
this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
}
/* ------------------------------------------------------------ */
public WebSocketConnectionD06(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
throws IOException
{
super(endpoint,timestamp);
// TODO - can we use the endpoint idle mechanism?
if (endpoint instanceof AsyncEndPoint)
((AsyncEndPoint)endpoint).cancelIdle();
_endp.setMaxIdleTime(maxIdleTime);
_websocket = websocket;
_generator = new WebSocketGeneratorD06(buffers, _endp,false);
_parser = new WebSocketParserD06(buffers, endpoint, _frameHandler,true);
// 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.Outbound getOutbound()
{
return _outbound;
}
/* ------------------------------------------------------------ */
public Connection handle() throws IOException
{
try
{
// handle the framing protocol
boolean progress=true;
while (progress)
{
int flushed=_generator.flush();
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
{
if (_endp.isOpen())
{
_idle.access(_endp);
if (_closedIn && _closedOut && _generator.isBufferEmpty())
_endp.close();
else
checkWriteable();
}
}
return this;
}
/* ------------------------------------------------------------ */
public boolean isIdle()
{
return _parser.isBufferEmpty() && _generator.isBufferEmpty();
}
/* ------------------------------------------------------------ */
@Override
public void idleExpired()
{
closeOut(WebSocket.CLOSE_NORMAL,"Idle");
}
/* ------------------------------------------------------------ */
public boolean isSuspended()
{
return false;
}
/* ------------------------------------------------------------ */
public void closed()
{
_websocket.onDisconnect();
}
/* ------------------------------------------------------------ */
public synchronized void closeIn(int code,String message)
{
Log.debug("ClosedIn {} {}",this,message);
try
{
if (_closedOut)
_endp.close();
else
closeOut(code,message);
}
catch(IOException e)
{
Log.ignore(e);
}
finally
{
_closedIn=true;
}
}
/* ------------------------------------------------------------ */
public synchronized void closeOut(int code,String message)
{
Log.debug("ClosedOut {} {}",this,message);
try
{
if (_closedIn || _closedOut)
_endp.close();
else if (code<=0)
{
_generator.addFrame(WebSocket.OP_CLOSE,NORMAL_CLOSE,0,2,_endp.getMaxIdleTime());
}
else
{
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1_CHARSET);
bytes[0]=(byte)(code/0xff);
bytes[1]=(byte)(code%0xff);
_generator.addFrame(WebSocket.OP_CLOSE,bytes,0,bytes.length,_endp.getMaxIdleTime());
}
_generator.flush();
}
catch(IOException e)
{
Log.ignore(e);
}
finally
{
_closedOut=true;
}
}
/* ------------------------------------------------------------ */
public void fillBuffersFrom(Buffer buffer)
{
_parser.fill(buffer);
}
/* ------------------------------------------------------------ */
private void checkWriteable()
{
if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
((AsyncEndPoint)_endp).scheduleWrite();
}
/* ------------------------------------------------------------ */
private interface IdleCheck
{
void access(EndPoint endp);
}
/* ------------------------------------------------------------ */
public void handshake(HttpServletRequest request, HttpServletResponse response, String origin, 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);
response.sendError(101);
_websocket.onConnect(_outbound);
}
/* ------------------------------------------------------------ */
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);
}
}
}

View File

@ -93,23 +93,30 @@ public class WebSocketFactory
* @throws IOException * @throws IOException
*/ */
public void upgrade(HttpServletRequest request,HttpServletResponse response, WebSocket websocket, String origin, String subprotocol) public void upgrade(HttpServletRequest request,HttpServletResponse response, WebSocket websocket, String origin, String subprotocol)
throws IOException throws IOException
{ {
if (!"WebSocket".equals(request.getHeader("Upgrade"))) if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
throw new IllegalStateException("!Upgrade:websocket"); throw new IllegalStateException("!Upgrade:websocket");
if (!"HTTP/1.1".equals(request.getProtocol())) if (!"HTTP/1.1".equals(request.getProtocol()))
throw new IllegalStateException("!HTTP/1.1"); throw new IllegalStateException("!HTTP/1.1");
int draft=request.getIntHeader("Sec-WebSocket-Draft"); int draft=request.getIntHeader("Sec-WebSocket-Version");
if (draft<0)
draft=request.getIntHeader("Sec-WebSocket-Draft");
HttpConnection http = HttpConnection.getCurrentConnection(); HttpConnection http = HttpConnection.getCurrentConnection();
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint(); ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
final WebSocketConnection connection; final WebSocketConnection connection;
switch(draft) switch(draft)
{ {
case 5: case 6:
connection=new WebSocketConnectionD05(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft); connection=new WebSocketConnectionD06(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft);
break; break;
case 5:
case 4:
case 3:
case 2:
throw new UnsupportedOperationException("Unsupported draft specification: "+draft);
default: default:
connection=new WebSocketConnectionD00(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft); connection=new WebSocketConnectionD00(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft);
} }

View File

@ -26,6 +26,6 @@ public interface WebSocketGenerator
boolean isBufferEmpty(); boolean isBufferEmpty();
void addFrame(byte opcode, String content, int maxIdleTime) throws IOException; void addFrame(byte opcode, String content, int maxIdleTime) throws IOException;
void addFrame(byte opcode, byte[] content, int offset, int length, int maxIdleTime) throws IOException; void addFrame(byte opcode, byte[] content, int offset, int length, int maxIdleTime) throws IOException;
void addFragment(boolean more,byte opcode, byte[] content, int offset, int length, int maxIdleTime) throws IOException; void addFragment(boolean last, byte opcode,byte[] content, int offset, int length, int maxIdleTime) throws IOException;
int flush(int maxIdleTime) throws IOException; int flush(int maxIdleTime) throws IOException;
} }

View File

@ -171,9 +171,9 @@ public class WebSocketGeneratorD00 implements WebSocketGenerator
return _buffer==null || _buffer.length()==0; return _buffer==null || _buffer.length()==0;
} }
public void addFragment(boolean more, byte opcode, byte[] content, int offset, int length, int maxIdleTime) throws IOException public void addFragment(boolean last,byte opcode, byte[] content, int offset, int length, int maxIdleTime) throws IOException
{ {
if (more) if (!last)
throw new UnsupportedOperationException("fragmented"); throw new UnsupportedOperationException("fragmented");
addFrame(opcode,content,offset,length,maxIdleTime); addFrame(opcode,content,offset,length,maxIdleTime);
} }

View File

@ -47,10 +47,10 @@ public class WebSocketGeneratorD01 implements WebSocketGenerator
public synchronized void addFrame(byte opcode,byte[] content, int offset, int length, int blockFor) throws IOException public synchronized void addFrame(byte opcode,byte[] content, int offset, int length, int blockFor) throws IOException
{ {
addFragment(false,opcode,content,offset,length,blockFor); addFragment(true,opcode,content,offset,length,blockFor);
} }
public synchronized void addFragment(boolean more, byte opcode, byte[] content, int offset, int length, int blockFor) throws IOException public synchronized void addFragment(boolean last,byte opcode, byte[] content, int offset, int length, int blockFor) throws IOException
{ {
if (_buffer==null) if (_buffer==null)
_buffer=_buffers.getDirectBuffer(); _buffer=_buffers.getDirectBuffer();
@ -69,10 +69,10 @@ public class WebSocketGeneratorD01 implements WebSocketGenerator
fragment=_buffer.capacity()-10; fragment=_buffer.capacity()-10;
bufferPut((byte)(0x80|opcode), blockFor); bufferPut((byte)(0x80|opcode), blockFor);
} }
else if (more) else if (last)
bufferPut((byte)(0x80|opcode), blockFor);
else
bufferPut(opcode, blockFor); bufferPut(opcode, blockFor);
else
bufferPut((byte)(0x80|opcode), blockFor);
if (fragment>0xffff) if (fragment>0xffff)
{ {

View File

@ -14,6 +14,8 @@
package org.eclipse.jetty.websocket; package org.eclipse.jetty.websocket;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
@ -27,82 +29,112 @@ import org.eclipse.jetty.io.EofException;
* threads will call the addMessage methods while other * threads will call the addMessage methods while other
* threads are flushing the generator. * threads are flushing the generator.
*/ */
public class WebSocketGeneratorD05 implements WebSocketGenerator public class WebSocketGeneratorD06 implements WebSocketGenerator
{ {
final private WebSocketBuffers _buffers; final private WebSocketBuffers _buffers;
final private EndPoint _endp; final private EndPoint _endp;
private Buffer _buffer; private Buffer _buffer;
private final boolean _masked;
private final byte[] _mask=new byte[4];
private final Random _random;
private int _m;
private boolean _opsent;
public WebSocketGeneratorD05(WebSocketBuffers buffers, EndPoint endp) public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp, boolean masked)
{ {
_buffers=buffers; _buffers=buffers;
_endp=endp; _endp=endp;
_masked=masked;
_random=_masked?new SecureRandom():null; // TODO share the Random
} }
public synchronized void addFrame(byte opcode,byte[] content, int blockFor) throws IOException public synchronized void addFrame(byte opcode,byte[] content, int blockFor) throws IOException
{ {
_opsent=false;
addFrame(opcode,content,0,content.length,blockFor); addFrame(opcode,content,0,content.length,blockFor);
} }
public synchronized void addFrame(byte opcode,byte[] content, int offset, int length, int blockFor) throws IOException public synchronized void addFrame(byte opcode,byte[] content, int offset, int length, int blockFor) throws IOException
{ {
addFragment(false,opcode,content,offset,length,blockFor); _opsent=false;
addFragment(true,opcode,content,offset,length,blockFor);
} }
public synchronized void addFragment(boolean more, byte opcode, byte[] content, int offset, int length, int blockFor) throws IOException public synchronized void addFragment(boolean last, byte opcode, byte[] content, int offset, int length, int blockFor) throws IOException
{ {
if (_buffer==null) if (_buffer==null)
_buffer=_buffers.getDirectBuffer(); _buffer=_masked?_buffers.getBuffer():_buffers.getDirectBuffer();
if (_buffer.space() == 0) int space=_masked?14:10;
expelBuffer(blockFor);
opcode = (byte)(opcode & 0x0f); do
while (length>0)
{ {
// slice a fragment off opcode = _opsent?WebSocket.OP_CONTINUATION:(byte)(opcode & 0x0f);
int fragment=length; _opsent=true;
if (fragment+10>_buffer.capacity())
{ int payload=length;
fragment=_buffer.capacity()-10; if (payload+space>_buffer.capacity())
bufferPut((byte)(0x80|opcode), blockFor); payload=_buffer.capacity()-space;
} else if (last)
else if (more) opcode|=(byte)0x80; // Set the FIN bit
bufferPut((byte)(0x80|opcode), blockFor);
else
bufferPut(opcode, blockFor);
if (fragment>0xffff) // ensure there is space for header
if (_buffer.space() <= space)
expelBuffer(blockFor);
// write mask
if (_masked)
{ {
bufferPut((byte)0x7f, blockFor); _random.nextBytes(_mask);
bufferPut((byte)((fragment>>56)&0x7f), blockFor); _m=0;
bufferPut((byte)((fragment>>48)&0xff), blockFor); _buffer.put(_mask);
bufferPut((byte)((fragment>>40)&0xff), blockFor);
bufferPut((byte)((fragment>>32)&0xff), blockFor);
bufferPut((byte)((fragment>>24)&0xff), blockFor);
bufferPut((byte)((fragment>>16)&0xff), blockFor);
bufferPut((byte)((fragment>>8)&0xff), blockFor);
bufferPut((byte)(fragment&0xff), blockFor);
} }
else if (fragment >=0x7e)
// write the opcode and length
if (payload>0xffff)
{ {
bufferPut((byte)126, blockFor); bufferPut(new byte[]{
bufferPut((byte)(fragment>>8), blockFor); opcode,
bufferPut((byte)(fragment&0xff), blockFor); (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)
{
bufferPut(new byte[]{
opcode,
(byte)0x7e,
(byte)(payload>>8),
(byte)(payload&0xff)});
} }
else else
{ {
bufferPut((byte)fragment, blockFor); bufferPut(opcode);
bufferPut((byte)payload);
} }
int remaining = fragment; // write payload
int remaining = payload;
while (remaining > 0) while (remaining > 0)
{ {
_buffer.compact(); _buffer.compact();
int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
_buffer.put(content, offset + (fragment - remaining), chunk);
if (_masked)
{
for (int i=0;i<chunk;i++)
bufferPut(content[offset+ (payload-remaining)+i]);
}
else
_buffer.put(content, offset + (payload - remaining), chunk);
remaining -= chunk; remaining -= chunk;
if (_buffer.space() > 0) if (_buffer.space() > 0)
{ {
@ -120,18 +152,24 @@ public class WebSocketGeneratorD05 implements WebSocketGenerator
} }
} }
} }
offset+=fragment; offset+=payload;
length-=fragment; length-=payload;
} }
while (length>0);
_opsent=!last;
} }
private synchronized void bufferPut(byte datum, long blockFor) throws IOException private synchronized void bufferPut(byte[] data) throws IOException
{ {
if (_buffer==null) if (_masked)
_buffer=_buffers.getDirectBuffer(); for (int i=0;i<data.length;i++)
_buffer.put(datum); data[i]^=_mask[+_m++%4];
if (_buffer.space() == 0) _buffer.put(data);
expelBuffer(blockFor); }
private synchronized void bufferPut(byte data) throws IOException
{
_buffer.put((byte)(data^_mask[+_m++%4]));
} }
public synchronized void addFrame(byte frame, String content, int blockFor) throws IOException public synchronized void addFrame(byte frame, String content, int blockFor) throws IOException

View File

@ -95,9 +95,11 @@ public abstract class WebSocketHandler extends HandlerWrapper
@Override @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{ {
if ("WebSocket".equals(request.getHeader("Upgrade"))) if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
{ {
String subprotocol=request.getHeader(request.getHeader("Sec-WebSocket-Key1")!=null?"Sec-WebSocket-Protocol":"WebSocket-Protocol"); String subprotocol=request.getHeader("Sec-WebSocket-Protocol");
if (subprotocol==null) // TODO remove once draft period is over
subprotocol=request.getHeader("WebSocket-Protocol");
WebSocket websocket=doWebSocketConnect(request,subprotocol); WebSocket websocket=doWebSocketConnect(request,subprotocol);
String host=request.getHeader("Host"); String host=request.getHeader("Host");

View File

@ -28,7 +28,7 @@ import org.eclipse.jetty.util.log.Log;
* Parser the WebSocket protocol. * Parser the WebSocket protocol.
* *
*/ */
public class WebSocketParserD05 implements WebSocketParser public class WebSocketParserD06 implements WebSocketParser
{ {
public enum State { public enum State {
MASK(0), OPCODE(1), LENGTH_7(2), LENGTH_16(4), LENGTH_63(10), DATA(10); MASK(0), OPCODE(1), LENGTH_7(2), LENGTH_16(4), LENGTH_63(10), DATA(10);
@ -53,7 +53,7 @@ public class WebSocketParserD05 implements WebSocketParser
private final boolean _masked; private final boolean _masked;
private State _state; private State _state;
private Buffer _buffer; private Buffer _buffer;
private boolean _more; private boolean _fin;
private byte _flags; private byte _flags;
private byte _opcode; private byte _opcode;
private int _count; private int _count;
@ -70,7 +70,7 @@ public class WebSocketParserD05 implements WebSocketParser
* @param endp * @param endp
* @param handler * @param handler
*/ */
public WebSocketParserD05(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean masked) public WebSocketParserD06(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean masked)
{ {
_buffers=buffers; _buffers=buffers;
_endp=endp; _endp=endp;
@ -157,7 +157,7 @@ public class WebSocketParserD05 implements WebSocketParser
b^=_mask[_m++%4]; b^=_mask[_m++%4];
_opcode=(byte)(b&0xf); _opcode=(byte)(b&0xf);
_flags=(byte)(b>>4); _flags=(byte)(b>>4);
_more=(_flags&8)!=0; _fin=(_flags&8)!=0;
_state=State.LENGTH_7; _state=State.LENGTH_7;
continue; continue;
@ -226,7 +226,7 @@ public class WebSocketParserD05 implements WebSocketParser
array[data.getIndex()+i]^=_mask[_m++%4]; array[data.getIndex()+i]^=_mask[_m++%4];
} }
_handler.onFrame(_more,_flags, _opcode, data); _handler.onFrame(!_fin,_flags, _opcode, data);
_count=0; _count=0;
_state=State.OPCODE; _state=State.OPCODE;

View File

@ -64,9 +64,9 @@ public abstract class WebSocketServlet extends HttpServlet
{ {
boolean hixie = request.getHeader("Sec-WebSocket-Key1")!=null; boolean hixie = request.getHeader("Sec-WebSocket-Key1")!=null;
String protocol=request.getHeader(hixie?"Sec-WebSocket-Protocol":"WebSocket-Protocol"); String protocol=request.getHeader("Sec-WebSocket-Protocol");
if (protocol==null) if (protocol==null) // TODO remove once draft period is over
protocol=request.getHeader("Sec-WebSocket-Protocol"); protocol=request.getHeader("WebSocket-Protocol");
WebSocket websocket=doWebSocketConnect(request,protocol); WebSocket websocket=doWebSocketConnect(request,protocol);
String host=request.getHeader("Host"); String host=request.getHeader("Host");

View File

@ -0,0 +1,198 @@
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 WebSocketGeneratorD06Test
{
private ByteArrayBuffer _out;
private WebSocketGenerator _generator;
ByteArrayEndPoint _endPoint;
WebSocketBuffers _buffers;
byte[] _mask = new byte[4];
int _m;
@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 WebSocketGeneratorD06(_buffers, _endPoint,false);
_generator.addFrame((byte)0x04,"Hell\uFF4F W\uFF4Frld",0);
_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 WebSocketGeneratorD06(_buffers, _endPoint,false);
String string = "Hell\uFF4F W\uFF4Frld";
byte[] bytes=string.getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x04,bytes,0,bytes.length,0);
_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 WebSocketGeneratorD06(_buffers, _endPoint,false);
byte[] b=new byte[150];
for (int i=0;i<b.length;i++)
b[i]=(byte)('0'+(i%10));
_generator.addFrame((byte)0x4,b,0,b.length,0);
_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 WebSocketGeneratorD06(_buffers, _endPoint,true);
_generator.addFrame((byte)0x04,"Hell\uFF4F W\uFF4Frld",0);
_generator.flush();
_out.get(_mask,0,4);
assertEquals((byte)0x84,getMasked());
assertEquals(15,0xff&getMasked());
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 WebSocketGeneratorD06(_buffers, _endPoint,true);
String string = "Hell\uFF4F W\uFF4Frld";
byte[] bytes=string.getBytes(StringUtil.__UTF8);
_generator.addFrame((byte)0x04,bytes,0,bytes.length,0);
_generator.flush();
_out.get(_mask,0,4);
assertEquals((byte)0x84,getMasked());
assertEquals(15,0xff&getMasked());
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 WebSocketGeneratorD06(_buffers, _endPoint,true);
byte[] b=new byte[150];
for (int i=0;i<b.length;i++)
b[i]=(byte)('0'+(i%10));
_generator.addFrame((byte)0x04,b,0,b.length,0);
_generator.flush();
_out.get(_mask,0,4);
assertEquals((byte)0x84,getMasked());
assertEquals((byte)126,getMasked());
assertEquals((byte)0,getMasked());
assertEquals((byte)b.length,getMasked());
for (int i=0;i<b.length;i++)
assertEquals('0'+(i%10),0xff&getMasked());
}
}

View File

@ -4,12 +4,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -21,8 +18,6 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;

View File

@ -0,0 +1,488 @@
package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Connector;
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.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* @version $Revision$ $Date$
*/
public class WebSocketMessageD06Test
{
private static Server _server;
private static Connector _connector;
private static TestWebSocket _serverWebSocket;
@BeforeClass
public static void startServer() throws Exception
{
_server = new Server();
_connector = new SelectChannelConnector();
_server.addConnector(_connector);
WebSocketHandler wsHandler = new WebSocketHandler()
{
@Override
protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
_serverWebSocket = new TestWebSocket();
_serverWebSocket.onConnect=("onConnect".equals(protocol));
_serverWebSocket.echo=("echo".equals(protocol));
return _serverWebSocket;
}
};
wsHandler.setMaxIdleTime(1000);
wsHandler.setHandler(new DefaultHandler());
_server.setHandler(wsHandler);
_server.start();
}
@AfterClass
public static void stopServer() throws Exception
{
_server.stop();
_server.join();
}
@Test
public void testHash()
{
assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD06.hashKey("dGhlIHNhbXBsZSBub25jZQ=="));
}
@Test
public void testServerSendBigStringMessage() 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: chat, superchat\r\n"+
"Sec-WebSocket-Version: 6\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("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.outbound);
// Server sends a big message
StringBuilder message = new StringBuilder();
String text = "0123456789ABCDEF";
for (int i = 0; i < (0x2000) / text.length(); i++)
message.append(text);
String data=message.toString();
_serverWebSocket.outbound.sendMessage(data);
assertEquals(WebSocket.OP_TEXT,input.read());
assertEquals(0x7e,input.read());
assertEquals(0x1f,input.read());
assertEquals(0xf6,input.read());
lookFor(data.substring(0,0x1ff6),input);
assertEquals(0x80,input.read());
assertEquals(0x0A,input.read());
lookFor(data.substring(0x1ff6),input);
}
@Test
public void testServerSendOnConnect() 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: 6\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("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.outbound);
assertEquals(0x84,input.read());
assertEquals(0x0f,input.read());
lookFor("sent on connect",input);
}
@Test
public void testServerEcho() 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: 6\r\n"+
"\r\n").getBytes("ISO-8859-1"));
output.flush();
output.write(0xff);
output.write(0xff);
output.write(0xff);
output.write(0xff);
output.write(0x84^0xff);
output.write(0x0f^0xff);
byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1_CHARSET);
for (int i=0;i<bytes.length;i++)
output.write(bytes[i]^0xff);
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("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.outbound);
assertEquals(0x84,input.read());
assertEquals(0x0f,input.read());
lookFor("this is an echo",input);
}
@Test
public void testServerPingPong() throws Exception
{
Socket socket = new Socket("localhost", _connector.getLocalPort());
// Make sure the read times out if there are problems with the implementation
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: 6\r\n"+
"\r\n").getBytes("ISO-8859-1"));
output.flush();
output.write(0xff);
output.write(0xff);
output.write(0xff);
output.write(0xff);
output.write(0x82^0xff);
output.write(0x00^0xff);
output.flush();
InputStream input = socket.getInputStream();
lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
skipTo("Sec-WebSocket-Accept: ",input);
lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
skipTo("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.outbound);
socket.setSoTimeout(1000);
assertEquals(0x83,input.read());
assertEquals(0x00,input.read());
}
@Test
public void testIdle() 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: 6\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(10000);
InputStream input = socket.getInputStream();
lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
skipTo("Sec-WebSocket-Accept: ",input);
lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
skipTo("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.outbound);
assertEquals(0x84,input.read());
assertEquals(0x0f,input.read());
lookFor("sent on connect",input);
assertEquals((byte)0x81,(byte)input.read());
assertEquals(0x06,input.read());
assertEquals(1000/0xff,input.read());
assertEquals(1000%0xff,input.read());
lookFor("Idle",input);
// respond to close
output.write(0xff);
output.write(0xff);
output.write(0xff);
output.write(0xff);
output.write(0x81^0xff);
output.write(0x00^0xff);
output.flush();
assertTrue(_serverWebSocket.awaitDisconnected(5000));
try
{
_serverWebSocket.outbound.sendMessage("Don't send");
assertTrue(false);
}
catch(IOException e)
{
assertTrue(true);
}
}
@Test
public void testClose() 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: 6\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("\r\n\r\n",input);
assertTrue(_serverWebSocket.awaitConnected(1000));
assertNotNull(_serverWebSocket.outbound);
assertEquals(0x84,input.read());
assertEquals(0x0f,input.read());
lookFor("sent on connect",input);
socket.close();
assertTrue(_serverWebSocket.awaitDisconnected(500));
try
{
_serverWebSocket.outbound.sendMessage("Don't send");
assertTrue(false);
}
catch(IOException e)
{
assertTrue(true);
}
}
private void lookFor(String string,InputStream in)
throws IOException
{
String orig=string;
Utf8StringBuilder scanned=new Utf8StringBuilder();
try
{
while(true)
{
int b = in.read();
if (b<0)
throw new EOFException();
scanned.append((byte)b);
assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
if (string.length()==1)
break;
string=string.substring(1);
}
}
catch(IOException e)
{
System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
throw e;
}
}
private void skipTo(String string,InputStream in)
throws IOException
{
int state=0;
while(true)
{
int b = in.read();
if (b<0)
throw new EOFException();
if (b==string.charAt(state))
{
state++;
if (state==string.length())
break;
}
else
state=0;
}
}
private static class TestWebSocket implements WebSocket
{
boolean onConnect=false;
boolean echo=true;
private final CountDownLatch connected = new CountDownLatch(1);
private final CountDownLatch disconnected = new CountDownLatch(1);
private volatile Outbound outbound;
public void onConnect(Outbound outbound)
{
this.outbound = outbound;
if (onConnect)
{
try
{
outbound.sendMessage("sent on connect");
}
catch(IOException e)
{
e.printStackTrace();
}
}
connected.countDown();
}
private boolean awaitConnected(long time) throws InterruptedException
{
return connected.await(time, TimeUnit.MILLISECONDS);
}
private boolean awaitDisconnected(long time) throws InterruptedException
{
return disconnected.await(time, TimeUnit.MILLISECONDS);
}
public void onMessage(byte opcode, String data)
{
if (echo)
{
try
{
outbound.sendMessage(opcode,data);
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
public void onMessage(byte opcode, byte[] data, int offset, int length)
{
if (echo)
{
try
{
outbound.sendMessage(opcode,data,offset,length);
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
public void onDisconnect()
{
disconnected.countDown();
}
public void onFragment(boolean more, byte opcode, byte[] data, int offset, int length)
{
if (echo)
{
try
{
outbound.sendFragment(more,opcode,data,offset,length);
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
}

View File

@ -20,7 +20,7 @@ import org.junit.Test;
/** /**
* @version $Revision$ $Date$ * @version $Revision$ $Date$
*/ */
public class WebSocketParserD05Test public class WebSocketParserD06Test
{ {
private ByteArrayBuffer _in; private ByteArrayBuffer _in;
private Handler _handler; private Handler _handler;
@ -33,7 +33,7 @@ public class WebSocketParserD05Test
WebSocketBuffers buffers = new WebSocketBuffers(1024); WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
_handler = new Handler(); _handler = new Handler();
_parser=new WebSocketParserD05(buffers, endPoint,_handler,true); _parser=new WebSocketParserD06(buffers, endPoint,_handler,true);
_in = new ByteArrayBuffer(2048) _in = new ByteArrayBuffer(2048)
{ {
{ {
@ -104,7 +104,7 @@ public class WebSocketParserD05Test
@Test @Test
public void testShortText() throws Exception public void testShortText() throws Exception
{ {
_in.put((byte)0x00); _in.put((byte)0x84);
_in.put((byte)11); _in.put((byte)11);
_in.put("Hello World".getBytes(StringUtil.__UTF8)); _in.put("Hello World".getBytes(StringUtil.__UTF8));
@ -122,7 +122,7 @@ public class WebSocketParserD05Test
String string = "Hell\uFF4f W\uFF4Frld"; String string = "Hell\uFF4f W\uFF4Frld";
byte[] bytes = string.getBytes("UTF-8"); byte[] bytes = string.getBytes("UTF-8");
_in.put((byte)0x00); _in.put((byte)0x84);
_in.put((byte)bytes.length); _in.put((byte)bytes.length);
_in.put(bytes); _in.put(bytes);
@ -144,7 +144,7 @@ public class WebSocketParserD05Test
byte[] bytes = string.getBytes("UTF-8"); byte[] bytes = string.getBytes("UTF-8");
_in.put((byte)0x00); _in.put((byte)0x84);
_in.put((byte)0x7E); _in.put((byte)0x7E);
_in.put((byte)(bytes.length>>8)); _in.put((byte)(bytes.length>>8));
_in.put((byte)(bytes.length&0xff)); _in.put((byte)(bytes.length&0xff));
@ -164,7 +164,7 @@ public class WebSocketParserD05Test
WebSocketBuffers buffers = new WebSocketBuffers(0x20000); WebSocketBuffers buffers = new WebSocketBuffers(0x20000);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
WebSocketParser parser=new WebSocketParserD01(buffers, endPoint,_handler); WebSocketParser parser=new WebSocketParserD06(buffers, endPoint,_handler,false);
ByteArrayBuffer in = new ByteArrayBuffer(0x20000); ByteArrayBuffer in = new ByteArrayBuffer(0x20000);
endPoint.setIn(in); endPoint.setIn(in);
@ -176,7 +176,7 @@ public class WebSocketParserD05Test
byte[] bytes = string.getBytes("UTF-8"); byte[] bytes = string.getBytes("UTF-8");
in.put((byte)0x00); in.put((byte)0x84);
in.put((byte)0x7F); in.put((byte)0x7F);
in.put((byte)0x00); in.put((byte)0x00);
in.put((byte)0x00); in.put((byte)0x00);
@ -199,10 +199,10 @@ public class WebSocketParserD05Test
@Test @Test
public void testShortFragmentTest() throws Exception public void testShortFragmentTest() throws Exception
{ {
_in.put((byte)0x80); _in.put((byte)0x04);
_in.put((byte)0x06); _in.put((byte)0x06);
_in.put("Hello ".getBytes(StringUtil.__UTF8)); _in.put("Hello ".getBytes(StringUtil.__UTF8));
_in.put((byte)0x00); _in.put((byte)0x80);
_in.put((byte)0x05); _in.put((byte)0x05);
_in.put("World".getBytes(StringUtil.__UTF8)); _in.put("World".getBytes(StringUtil.__UTF8));
@ -232,11 +232,11 @@ public class WebSocketParserD05Test
if (more) if (more)
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length()); _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
else if (_utf8.length()==0) else if (_utf8.length()==0)
_data.add(opcode,buffer.toString("utf-8")); _data.add(buffer.toString("utf-8"));
else else
{ {
_utf8.append(buffer.array(),buffer.getIndex(),buffer.length()); _utf8.append(buffer.array(),buffer.getIndex(),buffer.length());
_data.add(opcode,_utf8.toString()); _data.add(_utf8.toString());
_utf8.reset(); _utf8.reset();
} }
} }