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:
Greg Wilkins 2011-03-01 06:10:15 +00:00
parent 75960baeb1
commit 0a0deacb62
4 changed files with 204 additions and 220 deletions

View File

@ -18,6 +18,7 @@ import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.ConnectedEndPoint;
import org.eclipse.jetty.server.HttpConnection;
@ -116,7 +117,7 @@ public class WebSocketFactory
case 4:
case 3:
case 2:
throw new UnsupportedOperationException("Unsupported draft specification: "+draft);
throw new HttpException(400,"Unsupported draft specification: "+draft);
default:
connection=new WebSocketConnectionD00(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime,draft);
}

View File

@ -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();
}
}
}

View File

@ -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));
}
*/
}

View File

@ -7,6 +7,7 @@ import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
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)
{
System.err.println("onMessage " + TypeUtil.toHexString(data,offset,length));
}
public void onMessage(final byte frame, final String data)