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.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);
|
||||
}
|
||||
|
|
|
@ -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.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)
|
||||
|
|
Loading…
Reference in New Issue