337685 Work in progress on draft 6 websockets
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2834 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
parent
75960baeb1
commit
0a0deacb62
|
@ -18,6 +18,7 @@ import java.io.IOException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpException;
|
||||||
import org.eclipse.jetty.http.HttpParser;
|
import org.eclipse.jetty.http.HttpParser;
|
||||||
import org.eclipse.jetty.io.ConnectedEndPoint;
|
import org.eclipse.jetty.io.ConnectedEndPoint;
|
||||||
import org.eclipse.jetty.server.HttpConnection;
|
import org.eclipse.jetty.server.HttpConnection;
|
||||||
|
@ -116,7 +117,7 @@ public class WebSocketFactory
|
||||||
case 4:
|
case 4:
|
||||||
case 3:
|
case 3:
|
||||||
case 2:
|
case 2:
|
||||||
throw new UnsupportedOperationException("Unsupported draft specification: "+draft);
|
throw new HttpException(400,"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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.Buffer;
|
||||||
|
import org.eclipse.jetty.io.bio.SocketEndPoint;
|
||||||
|
import org.eclipse.jetty.util.B64Code;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version $Revision$ $Date$
|
||||||
|
*/
|
||||||
|
public class WebSocketPingD06
|
||||||
|
{
|
||||||
|
private final static Random __random = new SecureRandom();
|
||||||
|
private final String _host;
|
||||||
|
private final int _port;
|
||||||
|
private final int _size=64;
|
||||||
|
private final Socket _socket;
|
||||||
|
private final BufferedWriter _output;
|
||||||
|
private final BufferedReader _input;
|
||||||
|
private final SocketEndPoint _endp;
|
||||||
|
private final WebSocketGeneratorD06 _generator;
|
||||||
|
private final WebSocketParserD06 _parser;
|
||||||
|
private int _sent;
|
||||||
|
private int _received;
|
||||||
|
private long _totalTime;
|
||||||
|
private long _minDuration=Long.MAX_VALUE;
|
||||||
|
private long _maxDuration=Long.MIN_VALUE;
|
||||||
|
private long _start;
|
||||||
|
private BlockingQueue<Long> _starts = new LinkedBlockingQueue<Long>();
|
||||||
|
private BlockingQueue<String> _pending = new LinkedBlockingQueue<String>();
|
||||||
|
private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler()
|
||||||
|
{
|
||||||
|
public synchronized void onFrame(boolean more, byte flags, byte opcode, Buffer buffer)
|
||||||
|
{
|
||||||
|
|
||||||
|
long start=_starts.poll();
|
||||||
|
String data=_pending.poll();
|
||||||
|
|
||||||
|
while (!data.equals(TypeUtil.toHexString(buffer.asArray())) && !_starts.isEmpty() && !_pending.isEmpty())
|
||||||
|
{
|
||||||
|
// Missed response
|
||||||
|
start=_starts.poll();
|
||||||
|
data=_pending.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
_received++;
|
||||||
|
|
||||||
|
long duration = System.nanoTime()-start;
|
||||||
|
if (duration>_maxDuration)
|
||||||
|
_maxDuration=duration;
|
||||||
|
if (duration<_minDuration)
|
||||||
|
_minDuration=duration;
|
||||||
|
_totalTime+=duration;
|
||||||
|
System.out.print(buffer.length()+" bytes from "+_host+": req="+_received+" time=");
|
||||||
|
System.out.printf("%.1fms\n",((double)duration/1000000.0));
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public WebSocketPingD06(String host, int port,int timeoutMS) throws IOException
|
||||||
|
{
|
||||||
|
_host=host;
|
||||||
|
_port=port;
|
||||||
|
_socket = new Socket(host, port);
|
||||||
|
_socket.setSoTimeout(timeoutMS);
|
||||||
|
_output = new BufferedWriter(new OutputStreamWriter(_socket.getOutputStream(), "ISO-8859-1"));
|
||||||
|
_input = new BufferedReader(new InputStreamReader(_socket.getInputStream(), "ISO-8859-1"));
|
||||||
|
|
||||||
|
_endp=new SocketEndPoint(_socket);
|
||||||
|
_generator = new WebSocketGeneratorD06(new WebSocketBuffers(32*1024),_endp,new WebSocketGeneratorD06.FixedMaskGen());
|
||||||
|
_parser = new WebSocketParserD06(new WebSocketBuffers(32*1024),_endp,_handler,false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void open() throws IOException
|
||||||
|
{
|
||||||
|
System.out.println("Jetty WebSocket PING "+_host+":"+_port+
|
||||||
|
" ("+_socket.getRemoteSocketAddress()+") " +_size+" bytes of data.");
|
||||||
|
byte[] key = new byte[16];
|
||||||
|
__random.nextBytes(key);
|
||||||
|
|
||||||
|
_output.write("GET /chat HTTP/1.1\r\n"+
|
||||||
|
"Host: "+_host+":"+_port+"\r\n"+
|
||||||
|
"Upgrade: websocket\r\n"+
|
||||||
|
"Connection: Upgrade\r\n"+
|
||||||
|
"Sec-WebSocket-Key: "+new String(B64Code.encode(key))+"\r\n"+
|
||||||
|
"Sec-WebSocket-Origin: http://example.com\r\n"+
|
||||||
|
"Sec-WebSocket-Protocol: lws-mirror-protocol\r\n" +
|
||||||
|
"Sec-WebSocket-Version: 6\r\n"+
|
||||||
|
"\r\n");
|
||||||
|
_output.flush();
|
||||||
|
|
||||||
|
String responseLine = _input.readLine();
|
||||||
|
if(!responseLine.startsWith("HTTP/1.1 101 Switching Protocols"))
|
||||||
|
throw new IOException(responseLine);
|
||||||
|
// Read until we find Response key
|
||||||
|
String line;
|
||||||
|
boolean accepted=false;
|
||||||
|
String protocol="";
|
||||||
|
while ((line = _input.readLine()) != null)
|
||||||
|
{
|
||||||
|
if (line.length() == 0)
|
||||||
|
break;
|
||||||
|
if (line.startsWith("Sec-WebSocket-Accept:"))
|
||||||
|
{
|
||||||
|
String accept=line.substring(21).trim();
|
||||||
|
accepted=accept.equals(WebSocketConnectionD06.hashKey(new String(B64Code.encode(key))));
|
||||||
|
}
|
||||||
|
else if (line.startsWith("Sec-WebSocket-Protocol:"))
|
||||||
|
{
|
||||||
|
protocol=line.substring(24).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accepted)
|
||||||
|
throw new IOException("Bad Sec-WebSocket-Accept");
|
||||||
|
System.out.println("handshake OK for protocol "+protocol);
|
||||||
|
|
||||||
|
new Thread()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while (_endp.isOpen())
|
||||||
|
_parser.parseNext();
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
_start=System.currentTimeMillis();
|
||||||
|
for (int i=0;i<10;i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte data[] = new byte[_size];
|
||||||
|
__random.nextBytes(data);
|
||||||
|
|
||||||
|
|
||||||
|
_starts.add(System.nanoTime());
|
||||||
|
_pending.add(TypeUtil.toHexString(data));
|
||||||
|
_sent++;
|
||||||
|
_generator.addFrame(WebSocket.OP_PING,data,_socket.getSoTimeout());
|
||||||
|
_generator.flush(_socket.getSoTimeout());
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void dump() throws IOException
|
||||||
|
{
|
||||||
|
_socket.close();
|
||||||
|
long duration=System.currentTimeMillis()-_start;
|
||||||
|
System.out.println("--- "+_host+" websocket ping statistics using 1 connection ---");
|
||||||
|
System.out.println(_sent+" packets transmitted, "+_received+" received, "+
|
||||||
|
String.format("%d",100*(_sent-_received)/_sent)+"% loss, time "+duration+"ms");
|
||||||
|
System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",_minDuration/1000000.0,_totalTime/_received/1000000.0,_maxDuration/1000000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
WebSocketPingD06 ping = new WebSocketPingD06("localhost",8080,10000);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ping.open();
|
||||||
|
ping.run();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ping.dump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,219 +0,0 @@
|
||||||
package org.eclipse.jetty.websocket;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
|
||||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
|
||||||
import org.eclipse.jetty.server.LocalConnector;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
|
||||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class WebSocketTest
|
|
||||||
{
|
|
||||||
private static TestWebSocket _websocket;
|
|
||||||
private static LocalConnector _connector;
|
|
||||||
private static Server _server;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void startServer() throws Exception
|
|
||||||
{
|
|
||||||
_server = new Server();
|
|
||||||
_connector = new LocalConnector();
|
|
||||||
_server.addConnector(_connector);
|
|
||||||
WebSocketHandler handler = new WebSocketHandler()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
|
|
||||||
{
|
|
||||||
_websocket = new TestWebSocket();
|
|
||||||
return _websocket;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handler.setHandler(new DefaultHandler());
|
|
||||||
_server.setHandler(handler);
|
|
||||||
|
|
||||||
_server.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void stopServer() throws Exception
|
|
||||||
{
|
|
||||||
_server.stop();
|
|
||||||
_server.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHixieCrypt() throws Exception
|
|
||||||
{
|
|
||||||
assertEquals(155712099,WebSocketConnectionD00.hixieCrypt("18x 6]8vM;54 *(5: { U1]8 z [ 8"));
|
|
||||||
assertEquals(173347027,WebSocketConnectionD00.hixieCrypt("1_ tx7X d < nw 334J702) 7]o}` 0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHixie() throws Exception
|
|
||||||
{
|
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
||||||
byte[] result;
|
|
||||||
byte[] expected;
|
|
||||||
|
|
||||||
expected=md.digest(TypeUtil.fromHexString("00000000000000000000000000000000"));
|
|
||||||
result=WebSocketConnectionD00.doTheHixieHixieShake(
|
|
||||||
0 ,0, new byte[8]);
|
|
||||||
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
|
|
||||||
|
|
||||||
expected=md.digest(TypeUtil.fromHexString("01020304050607080000000000000000"));
|
|
||||||
result=WebSocketConnectionD00.doTheHixieHixieShake(
|
|
||||||
0x01020304,
|
|
||||||
0x05060708,
|
|
||||||
new byte[8]);
|
|
||||||
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
|
|
||||||
|
|
||||||
byte[] random = new byte[8];
|
|
||||||
for (int i=0;i<8;i++)
|
|
||||||
random[i]=(byte)(0xff&"Tm[K T2u".charAt(i));
|
|
||||||
result=WebSocketConnectionD00.doTheHixieHixieShake(
|
|
||||||
155712099,173347027,random);
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i=0;i<16;i++)
|
|
||||||
b.append((char)result[i]);
|
|
||||||
assertEquals("fQJ,fN/4F4!~K~MH",b.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNoWebSocket() throws Exception
|
|
||||||
{
|
|
||||||
String response = _connector.getResponses(
|
|
||||||
"GET /foo HTTP/1.1\r\n" +
|
|
||||||
"Host: localhost\r\n" +
|
|
||||||
"\r\n",false);
|
|
||||||
|
|
||||||
assertTrue(response.startsWith("HTTP/1.1 404 "));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOpenWebSocket() throws Exception
|
|
||||||
{
|
|
||||||
String response = _connector.getResponses(
|
|
||||||
"GET /demo HTTP/1.1\r\n" +
|
|
||||||
"Host: localhost\r\n" +
|
|
||||||
"Upgrade: WebSocket\r\n" +
|
|
||||||
"Connection: Upgrade\r\n" +
|
|
||||||
"\r\n",false);
|
|
||||||
|
|
||||||
assertTrue(response.startsWith("HTTP/1.1 101 Web Socket Protocol Handshake"));
|
|
||||||
assertTrue(response.contains("Upgrade: WebSocket"));
|
|
||||||
assertTrue(response.contains("Connection: Upgrade"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSendReceiveUtf8WebSocket() throws Exception
|
|
||||||
{
|
|
||||||
ByteArrayBuffer buffer = new ByteArrayBuffer(1024);
|
|
||||||
|
|
||||||
buffer.put(
|
|
||||||
("GET /demo HTTP/1.1\r\n" +
|
|
||||||
"Host: localhost\r\n" +
|
|
||||||
"Upgrade: WebSocket\r\n" +
|
|
||||||
"Connection: Upgrade\r\n" +
|
|
||||||
"\r\n").getBytes(StringUtil.__ISO_8859_1));
|
|
||||||
|
|
||||||
buffer.put((byte)0);
|
|
||||||
buffer.put("Hello World".getBytes(StringUtil.__UTF8));
|
|
||||||
buffer.put((byte)0xFF);
|
|
||||||
|
|
||||||
ByteArrayBuffer out = _connector.getResponses(buffer,true);
|
|
||||||
|
|
||||||
String response = StringUtil.printable(out.asArray());
|
|
||||||
|
|
||||||
assertTrue(response.startsWith("HTTP/1.1 101 Web Socket Protocol Handshake"));
|
|
||||||
assertTrue(response.contains("Upgrade: WebSocket"));
|
|
||||||
assertTrue(response.contains("Connection: Upgrade"));
|
|
||||||
assertTrue(response.contains("0x00Roger That0xFF"));
|
|
||||||
|
|
||||||
assertEquals("Hello World",_websocket._utf8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TestWebSocket implements WebSocket
|
|
||||||
{
|
|
||||||
Outbound _outbound;
|
|
||||||
Buffer _binary;
|
|
||||||
String _utf8;
|
|
||||||
boolean _disconnected;
|
|
||||||
|
|
||||||
public void onConnect(Outbound outbound)
|
|
||||||
{
|
|
||||||
_outbound=outbound;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_outbound.sendMessage("Roger That");
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
Log.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMessage(byte frame, byte[] data,int offset, int length)
|
|
||||||
{
|
|
||||||
_binary=new ByteArrayBuffer(data,offset,length).duplicate(Buffer.READONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMessage(byte frame, String data)
|
|
||||||
{
|
|
||||||
_utf8=data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDisconnect()
|
|
||||||
{
|
|
||||||
_disconnected=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onFragment(boolean more, byte opcode, byte[] data, int offset, int length)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* draft 03 nonce proposal
|
|
||||||
public static void main(String[] arg) throws Exception
|
|
||||||
{
|
|
||||||
String cnonce="A23F2BCA452DDE01";
|
|
||||||
byte[] cnb = TypeUtil.fromHexString(cnonce);
|
|
||||||
|
|
||||||
String snonce="15F0D2278BCD457F";
|
|
||||||
byte[] snb = TypeUtil.fromHexString(snonce);
|
|
||||||
|
|
||||||
|
|
||||||
MessageDigest md=MessageDigest.getInstance("MD5");
|
|
||||||
md.update(cnb);
|
|
||||||
md.update("WebSocket".getBytes("ASCII"));
|
|
||||||
md.update(snb);
|
|
||||||
|
|
||||||
byte[] digest = md.digest();
|
|
||||||
|
|
||||||
System.err.println(TypeUtil.toHexString(digest));
|
|
||||||
|
|
||||||
md.reset();
|
|
||||||
md.update(snb);
|
|
||||||
md.update("WebSocket".getBytes("ASCII"));
|
|
||||||
md.update(cnb);
|
|
||||||
digest = md.digest();
|
|
||||||
|
|
||||||
System.err.println(TypeUtil.toHexString(digest));
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
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.util.TypeUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
|
||||||
public class WebSocketTestServer extends Server
|
public class WebSocketTestServer extends Server
|
||||||
|
@ -51,6 +52,7 @@ public class WebSocketTestServer extends Server
|
||||||
|
|
||||||
public void onMessage(byte frame, byte[] data, int offset, int length)
|
public void onMessage(byte frame, byte[] data, int offset, int length)
|
||||||
{
|
{
|
||||||
|
System.err.println("onMessage " + TypeUtil.toHexString(data,offset,length));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMessage(final byte frame, final String data)
|
public void onMessage(final byte frame, final String data)
|
||||||
|
|
Loading…
Reference in New Issue