324369 Improved handling of multiple versions of draft-ietf-hybi-thewebsocketprotocol
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2342 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
parent
fd90e82e64
commit
de178f18c6
|
@ -2,6 +2,7 @@ jetty-7.2.0.RC1-SNAPSHOT
|
||||||
+ 289540 added javadoc into distribution
|
+ 289540 added javadoc into distribution
|
||||||
+ 297154 add source distribution artifact
|
+ 297154 add source distribution artifact
|
||||||
+ 323985 Xmlconfiguration pulls start.jar config properties
|
+ 323985 Xmlconfiguration pulls start.jar config properties
|
||||||
|
+ 324369 Improved handling of multiple versions of draft-ietf-hybi-thewebsocketprotocol
|
||||||
+ 326734 Configure Digest maxNonceAge with Security handler init param
|
+ 326734 Configure Digest maxNonceAge with Security handler init param
|
||||||
+ 327109 Fixed AJP handling of empty packets
|
+ 327109 Fixed AJP handling of empty packets
|
||||||
+ 327183 Allow better configurability of HttpClient for TLS/SSL
|
+ 327183 Allow better configurability of HttpClient for TLS/SSL
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||||
import org.eclipse.jetty.websocket.WebSocket;
|
import org.eclipse.jetty.websocket.WebSocket;
|
||||||
import org.eclipse.jetty.websocket.WebSocketConnection;
|
import org.eclipse.jetty.websocket.WebSocketConnectionD00;
|
||||||
import org.eclipse.jetty.websocket.WebSocketHandler;
|
import org.eclipse.jetty.websocket.WebSocketHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,7 +123,7 @@ public class WebSocketUpgradeTest extends TestCase
|
||||||
protected Connection onSwitchProtocol(EndPoint endp) throws IOException
|
protected Connection onSwitchProtocol(EndPoint endp) throws IOException
|
||||||
{
|
{
|
||||||
waitFor(3);
|
waitFor(3);
|
||||||
WebSocketConnection connection = new WebSocketConnection(clientWS,endp,0);
|
WebSocketConnectionD00 connection = new WebSocketConnectionD00(clientWS,endp,0);
|
||||||
|
|
||||||
_results.add("onSwitchProtocol");
|
_results.add("onSwitchProtocol");
|
||||||
_results.add(connection);
|
_results.add(connection);
|
||||||
|
@ -164,7 +164,7 @@ public class WebSocketUpgradeTest extends TestCase
|
||||||
assertEquals(new Integer(101), _results.poll(1,TimeUnit.SECONDS));
|
assertEquals(new Integer(101), _results.poll(1,TimeUnit.SECONDS));
|
||||||
|
|
||||||
assertEquals("onSwitchProtocol", _results.poll(1,TimeUnit.SECONDS));
|
assertEquals("onSwitchProtocol", _results.poll(1,TimeUnit.SECONDS));
|
||||||
WebSocketConnection client_conn=(WebSocketConnection)_results.poll(1,TimeUnit.SECONDS);
|
WebSocketConnectionD00 client_conn=(WebSocketConnectionD00)_results.poll(1,TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertEquals("clientWS.onConnect", _results.poll(1,TimeUnit.SECONDS));
|
assertEquals("clientWS.onConnect", _results.poll(1,TimeUnit.SECONDS));
|
||||||
assertEquals(client_conn, _results.poll(1,TimeUnit.SECONDS));
|
assertEquals(client_conn, _results.poll(1,TimeUnit.SECONDS));
|
||||||
|
|
|
@ -22,12 +22,12 @@ final class FrameHandlerD1 implements WebSocketParser.FrameHandler
|
||||||
public final static byte PING=1;
|
public final static byte PING=1;
|
||||||
public final static byte PONG=1;
|
public final static byte PONG=1;
|
||||||
|
|
||||||
final WebSocketConnection _connection;
|
final WebSocketConnectionD00 _connection;
|
||||||
final WebSocket _websocket;
|
final WebSocket _websocket;
|
||||||
final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
|
final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
|
||||||
boolean _fragmented=false;
|
boolean _fragmented=false;
|
||||||
|
|
||||||
FrameHandlerD1(WebSocketConnection connection, WebSocket websocket)
|
FrameHandlerD1(WebSocketConnectionD00 connection, WebSocket websocket)
|
||||||
{
|
{
|
||||||
_connection=connection;
|
_connection=connection;
|
||||||
_websocket=websocket;
|
_websocket=websocket;
|
||||||
|
|
|
@ -1,328 +1,17 @@
|
||||||
// ========================================================================
|
|
||||||
// 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;
|
package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.AsyncEndPoint;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
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.nio.IndirectNIOBuffer;
|
|
||||||
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
|
|
||||||
public class WebSocketConnection implements Connection, WebSocket.Outbound
|
public interface WebSocketConnection extends Connection, WebSocket.Outbound
|
||||||
{
|
{
|
||||||
final IdleCheck _idle;
|
void fillBuffersFrom(Buffer buffer);
|
||||||
final EndPoint _endp;
|
|
||||||
final WebSocketParser _parser;
|
|
||||||
final WebSocketGenerator _generator;
|
|
||||||
final long _timestamp;
|
|
||||||
final WebSocket _websocket;
|
|
||||||
String _key1;
|
|
||||||
String _key2;
|
|
||||||
ByteArrayBuffer _hixieBytes;
|
|
||||||
|
|
||||||
public WebSocketConnection(WebSocket websocket, EndPoint endpoint,int draft)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketConnection(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
|
void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException;
|
||||||
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;
|
|
||||||
|
|
||||||
// Select the parser/generators to use
|
|
||||||
switch(draft)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
_generator = new WebSocketGeneratorD01(buffers, _endp);
|
|
||||||
_parser = new WebSocketParserD01(buffers, endpoint, new FrameHandlerD1(this,_websocket));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_generator = new WebSocketGeneratorD00(buffers, _endp);
|
|
||||||
_parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD0(_websocket));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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=WebSocketConnection.doTheHixieHixieShake(
|
|
||||||
WebSocketConnection.hixieCrypt(_key1),
|
|
||||||
WebSocketConnection.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(String content) throws IOException
|
|
||||||
{
|
|
||||||
sendMessage(WebSocket.SENTINEL_FRAME,content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(byte frame, String content) throws IOException
|
|
||||||
{
|
|
||||||
_generator.addFrame(frame,content,_endp.getMaxIdleTime());
|
|
||||||
_generator.flush();
|
|
||||||
checkWriteable();
|
|
||||||
_idle.access(_endp);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 fill(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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,383 @@
|
||||||
|
// ========================================================================
|
||||||
|
// 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 WebSocketConnectionD00 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 WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint,int draft)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocketConnectionD00(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;
|
||||||
|
|
||||||
|
// Select the parser/generators to use
|
||||||
|
switch(draft)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
_generator = new WebSocketGeneratorD01(buffers, _endp);
|
||||||
|
_parser = new WebSocketParserD01(buffers, endpoint, new FrameHandlerD1(this,_websocket));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_generator = new WebSocketGeneratorD00(buffers, _endp);
|
||||||
|
_parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD0(_websocket));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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=WebSocketConnectionD00.doTheHixieHixieShake(
|
||||||
|
WebSocketConnectionD00.hixieCrypt(_key1),
|
||||||
|
WebSocketConnectionD00.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -89,11 +89,11 @@ public class WebSocketFactory
|
||||||
* @param response The response to upgrade
|
* @param response The response to upgrade
|
||||||
* @param websocket The websocket handler implementation to use
|
* @param websocket The websocket handler implementation to use
|
||||||
* @param origin The origin of the websocket connection
|
* @param origin The origin of the websocket connection
|
||||||
* @param protocol The protocol
|
* @param subprotocol The protocol
|
||||||
* @throws UpgradeConnectionException Thrown to upgrade the connection
|
* @throws UpgradeConnectionException Thrown to upgrade the connection
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void upgrade(HttpServletRequest request,HttpServletResponse response, WebSocket websocket, String origin, String protocol)
|
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".equals(request.getHeader("Upgrade")))
|
||||||
|
@ -104,48 +104,23 @@ public class WebSocketFactory
|
||||||
int draft=request.getIntHeader("Sec-WebSocket-Draft");
|
int draft=request.getIntHeader("Sec-WebSocket-Draft");
|
||||||
HttpConnection http = HttpConnection.getCurrentConnection();
|
HttpConnection http = HttpConnection.getCurrentConnection();
|
||||||
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
|
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
|
||||||
WebSocketConnection connection = new WebSocketConnection(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft);
|
|
||||||
|
|
||||||
String uri=request.getRequestURI();
|
final WebSocketConnection connection;
|
||||||
String query=request.getQueryString();
|
switch(draft)
|
||||||
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");
|
default:
|
||||||
connection.setHixieKeys(key1,key2);
|
connection=new WebSocketConnectionD00(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft);
|
||||||
|
|
||||||
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 (protocol!=null)
|
|
||||||
response.addHeader("Sec-WebSocket-Protocol",protocol);
|
|
||||||
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 (protocol!=null)
|
|
||||||
response.addHeader("WebSocket-Protocol",protocol);
|
|
||||||
response.sendError(101,"Web Socket Protocol Handshake");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let the connection finish processing the handshake
|
||||||
|
connection.handshake(request,response, origin, subprotocol);
|
||||||
response.flushBuffer();
|
response.flushBuffer();
|
||||||
|
|
||||||
connection.fill(((HttpParser)http.getParser()).getHeaderBuffer());
|
// Give the connection any unused data from the HTTP connection.
|
||||||
connection.fill(((HttpParser)http.getParser()).getBodyBuffer());
|
connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer());
|
||||||
|
connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer());
|
||||||
|
|
||||||
// connect here for -75, but in connection for -76 onwards
|
// Tell jetty about the new connection
|
||||||
if (key1==null)
|
|
||||||
websocket.onConnect(connection);
|
|
||||||
|
|
||||||
request.setAttribute("org.eclipse.jetty.io.Connection",connection);
|
request.setAttribute("org.eclipse.jetty.io.Connection",connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,15 +97,15 @@ public abstract class WebSocketHandler extends HandlerWrapper
|
||||||
{
|
{
|
||||||
if ("WebSocket".equals(request.getHeader("Upgrade")))
|
if ("WebSocket".equals(request.getHeader("Upgrade")))
|
||||||
{
|
{
|
||||||
String protocol=request.getHeader(request.getHeader("Sec-WebSocket-Key1")!=null?"Sec-WebSocket-Protocol":"WebSocket-Protocol");
|
String subprotocol=request.getHeader(request.getHeader("Sec-WebSocket-Key1")!=null?"Sec-WebSocket-Protocol":"WebSocket-Protocol");
|
||||||
WebSocket websocket=doWebSocketConnect(request,protocol);
|
WebSocket websocket=doWebSocketConnect(request,subprotocol);
|
||||||
|
|
||||||
String host=request.getHeader("Host");
|
String host=request.getHeader("Host");
|
||||||
String origin=request.getHeader("Origin");
|
String origin=request.getHeader("Origin");
|
||||||
origin=checkOrigin(request,host,origin);
|
origin=checkOrigin(request,host,origin);
|
||||||
|
|
||||||
if (websocket!=null)
|
if (websocket!=null)
|
||||||
_websocket.upgrade(request,response,websocket,origin,protocol);
|
_websocket.upgrade(request,response,websocket,origin,subprotocol);
|
||||||
else
|
else
|
||||||
response.sendError(503);
|
response.sendError(503);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,8 @@ public class WebSocketTest
|
||||||
@Test
|
@Test
|
||||||
public void testHixieCrypt() throws Exception
|
public void testHixieCrypt() throws Exception
|
||||||
{
|
{
|
||||||
assertEquals(155712099,WebSocketConnection.hixieCrypt("18x 6]8vM;54 *(5: { U1]8 z [ 8"));
|
assertEquals(155712099,WebSocketConnectionD00.hixieCrypt("18x 6]8vM;54 *(5: { U1]8 z [ 8"));
|
||||||
assertEquals(173347027,WebSocketConnection.hixieCrypt("1_ tx7X d < nw 334J702) 7]o}` 0"));
|
assertEquals(173347027,WebSocketConnectionD00.hixieCrypt("1_ tx7X d < nw 334J702) 7]o}` 0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -69,12 +69,12 @@ public class WebSocketTest
|
||||||
byte[] expected;
|
byte[] expected;
|
||||||
|
|
||||||
expected=md.digest(TypeUtil.fromHexString("00000000000000000000000000000000"));
|
expected=md.digest(TypeUtil.fromHexString("00000000000000000000000000000000"));
|
||||||
result=WebSocketConnection.doTheHixieHixieShake(
|
result=WebSocketConnectionD00.doTheHixieHixieShake(
|
||||||
0 ,0, new byte[8]);
|
0 ,0, new byte[8]);
|
||||||
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
|
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
|
||||||
|
|
||||||
expected=md.digest(TypeUtil.fromHexString("01020304050607080000000000000000"));
|
expected=md.digest(TypeUtil.fromHexString("01020304050607080000000000000000"));
|
||||||
result=WebSocketConnection.doTheHixieHixieShake(
|
result=WebSocketConnectionD00.doTheHixieHixieShake(
|
||||||
0x01020304,
|
0x01020304,
|
||||||
0x05060708,
|
0x05060708,
|
||||||
new byte[8]);
|
new byte[8]);
|
||||||
|
@ -83,7 +83,7 @@ public class WebSocketTest
|
||||||
byte[] random = new byte[8];
|
byte[] random = new byte[8];
|
||||||
for (int i=0;i<8;i++)
|
for (int i=0;i<8;i++)
|
||||||
random[i]=(byte)(0xff&"Tm[K T2u".charAt(i));
|
random[i]=(byte)(0xff&"Tm[K T2u".charAt(i));
|
||||||
result=WebSocketConnection.doTheHixieHixieShake(
|
result=WebSocketConnectionD00.doTheHixieHixieShake(
|
||||||
155712099,173347027,random);
|
155712099,173347027,random);
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue