From 1692b4f7c2d2b88aea1592ce77119e43543fd6d4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 20 Jun 2012 14:43:16 -0700 Subject: [PATCH 1/8] Adding ClosePayloadParser & Tests --- .../jetty/websocket/frames/CloseFrame.java | 7 ++- .../websocket/parser/ClosePayloadParser.java | 34 ++++++++++++- .../jetty/websocket/parser/Parser.java | 8 ++- .../parser/ClosePayloadParserTest.java | 50 +++++++++++++++++++ .../websocket/parser/MaskedByteBuffer.java | 32 ++++++++++++ .../parser/TextPayloadParserTest.java | 50 +++++++------------ 6 files changed, 145 insertions(+), 36 deletions(-) create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/MaskedByteBuffer.java diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java index 5fc7f8c4341..acaa1a98401 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java @@ -8,7 +8,7 @@ import org.eclipse.jetty.websocket.api.WebSocket; */ public class CloseFrame extends ControlFrame { - private final short statusCode; + private short statusCode; private String reason; public CloseFrame() @@ -43,6 +43,11 @@ public class CloseFrame extends ControlFrame this.reason = reason; } + public void setStatusCode(short statusCode) + { + this.statusCode = statusCode; + } + @Override public String toString() { diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/ClosePayloadParser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/ClosePayloadParser.java index ff1a3610116..b57ba797401 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/ClosePayloadParser.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/ClosePayloadParser.java @@ -2,6 +2,8 @@ package org.eclipse.jetty.websocket.parser; import java.nio.ByteBuffer; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.frames.CloseFrame; @@ -11,6 +13,8 @@ import org.eclipse.jetty.websocket.frames.CloseFrame; public class ClosePayloadParser extends FrameParser { private CloseFrame frame; + private ByteBuffer payload; + private int payloadLength; public ClosePayloadParser(WebSocketPolicy policy) { @@ -34,7 +38,35 @@ public class ClosePayloadParser extends FrameParser @Override public boolean parsePayload(ByteBuffer buffer) { - // TODO Auto-generated method stub + payloadLength = getFrame().getPayloadLength(); + if (payloadLength == 0) + { + // no status code. no reason. + return true; + } + + while (buffer.hasRemaining()) + { + if (payload == null) + { + getPolicy().assertValidBinaryMessageSize(payloadLength); + payload = ByteBuffer.allocate(payloadLength); + } + + copyBuffer(buffer,payload,payload.remaining()); + + if (payload.position() >= payloadLength) + { + payload.flip(); + frame.setStatusCode(payload.getShort()); + if (payload.remaining() > 0) + { + String reason = BufferUtil.toString(payload,StringUtil.__UTF8_CHARSET); + frame.setReason(reason); + } + return true; + } + } return false; } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/Parser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/Parser.java index 34cb64d765d..566e73b7bec 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/Parser.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/Parser.java @@ -109,7 +109,13 @@ public class Parser boolean rsv1 = ((b & 0x40) != 0); boolean rsv2 = ((b & 0x20) != 0); boolean rsv3 = ((b & 0x10) != 0); - OpCode opcode = OpCode.from((byte)(b & 0x0F)); + byte opc = (byte)(b & 0x0F); + OpCode opcode = OpCode.from(opc); + + if (opcode == null) + { + throw new WebSocketException("Unknown opcode: " + opc); + } if (opcode.isControlFrame() && !fin) { diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java new file mode 100644 index 00000000000..37e0e701ee8 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java @@ -0,0 +1,50 @@ +package org.eclipse.jetty.websocket.parser; + +import static org.hamcrest.Matchers.*; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.Debug; +import org.eclipse.jetty.websocket.api.WebSocket; +import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.frames.CloseFrame; +import org.junit.Assert; +import org.junit.Test; + +public class ClosePayloadParserTest +{ + @Test + public void testGameOver() + { + Debug.enableDebugLogging(Parser.class); + Debug.enableDebugLogging(ClosePayloadParser.class); + String expectedReason = "Game Over"; + + byte utf[] = expectedReason.getBytes(StringUtil.__UTF8_CHARSET); + ByteBuffer payload = ByteBuffer.allocate(utf.length + 2); + payload.putShort(WebSocket.CLOSE_NORMAL); + payload.put(utf,0,utf.length); + payload.flip(); + + ByteBuffer buf = ByteBuffer.allocate(24); + buf.put((byte)(0x80 | 0x08)); // fin + close + buf.put((byte)(0x80 | payload.remaining())); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,payload); + buf.flip(); + + WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); + Parser parser = new Parser(policy); + FrameParseCapture capture = new FrameParseCapture(); + parser.addListener(capture); + parser.parse(buf); + + capture.assertNoErrors(); + capture.assertHasFrame(CloseFrame.class,1); + CloseFrame txt = (CloseFrame)capture.getFrames().get(0); + Assert.assertThat("CloseFrame.statusCode",txt.getStatusCode(),is(WebSocket.CLOSE_NORMAL)); + Assert.assertThat("CloseFrame.data",txt.getReason(),is(expectedReason)); + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/MaskedByteBuffer.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/MaskedByteBuffer.java new file mode 100644 index 00000000000..ec1688fa561 --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/MaskedByteBuffer.java @@ -0,0 +1,32 @@ +package org.eclipse.jetty.websocket.parser; + +import java.nio.ByteBuffer; + +public class MaskedByteBuffer +{ + private static byte[] mask = new byte[] + { 0x00, (byte)0xF0, 0x0F, (byte)0xFF }; + + public static void putMask(ByteBuffer buffer) + { + buffer.put(mask,0,mask.length); + } + + public static void putPayload(ByteBuffer buffer, byte[] payload) + { + int len = payload.length; + for (int i = 0; i < len; i++) + { + buffer.put((byte)(payload[i] ^ mask[i % 4])); + } + } + + public static void putPayload(ByteBuffer buffer, ByteBuffer payload) + { + int len = payload.remaining(); + for (int i = 0; i < len; i++) + { + buffer.put((byte)(payload.get() ^ mask[i % 4])); + } + } +} diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java index 87f6780901a..3cbe159efdb 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java @@ -16,11 +16,8 @@ import org.junit.Test; public class TextPayloadParserTest { - private final byte[] mask = new byte[] - { 0x00, (byte)0xF0, 0x0F, (byte)0xFF }; - @Test - public void testFrameTooLargeDueToPolicyText() throws Exception + public void testFrameTooLargeDueToPolicy() throws Exception { WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); policy.setMaxTextMessageSize(1024); // set policy @@ -33,8 +30,8 @@ public class TextPayloadParserTest buf.put((byte)0x81); buf.put((byte)(0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length) buf.putShort((short)utf.length); - writeMask(buf); - writeMaskedPayload(buf,utf); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,utf); buf.flip(); Parser parser = new Parser(policy); @@ -68,8 +65,8 @@ public class TextPayloadParserTest buf.put((byte)0x81); buf.put((byte)(0x80 | 0x7F)); // 0x7F == 127 (a 4 byte payload length) buf.putInt(utf.length); - writeMask(buf); - writeMaskedPayload(buf,utf); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,utf); buf.flip(); WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); @@ -103,8 +100,8 @@ public class TextPayloadParserTest buf.put((byte)0x81); buf.put((byte)(0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length) buf.putShort((short)utf.length); - writeMask(buf); - writeMaskedPayload(buf,utf); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,utf); buf.flip(); WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); @@ -133,14 +130,14 @@ public class TextPayloadParserTest // part 1 buf.put((byte)0x01); // no fin + text buf.put((byte)(0x80 | b1.length)); - writeMask(buf); - writeMaskedPayload(buf,b1); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,b1); // part 2 buf.put((byte)0x80); // fin + continuation buf.put((byte)(0x80 | b2.length)); - writeMask(buf); - writeMaskedPayload(buf,b2); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,b2); buf.flip(); @@ -162,12 +159,13 @@ public class TextPayloadParserTest public void testShortMaskedText() throws Exception { String expectedText = "Hello World"; + byte utf[] = expectedText.getBytes(StringUtil.__UTF8_CHARSET); ByteBuffer buf = ByteBuffer.allocate(24); buf.put((byte)0x81); - buf.put((byte)(0x80 | expectedText.length())); - writeMask(buf); - writeMaskedPayload(buf,expectedText.getBytes(StringUtil.__UTF8)); + buf.put((byte)(0x80 | utf.length)); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,utf); buf.flip(); WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); @@ -192,8 +190,8 @@ public class TextPayloadParserTest ByteBuffer buf = ByteBuffer.allocate(24); buf.put((byte)0x81); buf.put((byte)(0x80 | utf.length)); - writeMask(buf); - writeMaskedPayload(buf,utf); + MaskedByteBuffer.putMask(buf); + MaskedByteBuffer.putPayload(buf,utf); buf.flip(); WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); @@ -207,18 +205,4 @@ public class TextPayloadParserTest TextFrame txt = (TextFrame)capture.getFrames().get(0); Assert.assertThat("TextFrame.data",txt.getData().toString(),is(expectedText)); } - - private void writeMask(ByteBuffer buf) - { - buf.put(mask,0,mask.length); - } - - private void writeMaskedPayload(ByteBuffer buf, byte[] bytes) - { - int len = bytes.length; - for (int i = 0; i < len; i++) - { - buf.put((byte)(bytes[i] ^ mask[i % 4])); - } - } } From 5f5d613eba0618bdf26edb3acaaae52fb7e89cff Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 20 Jun 2012 14:44:12 -0700 Subject: [PATCH 2/8] Removing old testcase (has replacements using new parser framework) --- .../websocket/WebSocketParserRFC6455Test.java | 392 ------------------ 1 file changed, 392 deletions(-) delete mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java deleted file mode 100644 index 79dad412a03..00000000000 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455Test.java +++ /dev/null @@ -1,392 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2011 Intalio, Inc. - * ====================================================================== - * 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; - - -public class WebSocketParserRFC6455Test -{ - /* - private ByteArrayEndPoint _endPoint; - private MaskedByteArrayBuffer _in; - private Handler _handler; - private WebSocketParserRFC6455 _parser; - private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF}; - private int _m; - - class MaskedByteArrayBuffer extends ByteArrayBuffer - { - MaskedByteArrayBuffer() - { - super(4096); - } - - public void sendMask() - { - super.poke(putIndex(),_mask,0,4); - super.setPutIndex(putIndex()+4); - _m=0; - } - - @Override - public int put(ByteBuffer src) - { - return put(src.asArray(),0,src.length()); - } - - public void putUnmasked(byte b) - { - super.put(b); - } - - @Override - public void put(byte b) - { - super.put((byte)(b^_mask[_m++%4])); - } - - @Override - public int put(byte[] b, int offset, int length) - { - byte[] mb = new byte[b.length]; - final int end=offset+length; - for (int i=offset;i0); - assertEquals(0xf,_handler._flags); - assertEquals(0xf,_handler._opcode); - assertTrue(_parser.isBufferEmpty()); - _parser.returnBuffer(); - assertTrue(_parser.getBuffer()==null); - } - - @Test - public void testShortText() throws Exception - { - _in.putUnmasked((byte)0x81); - _in.putUnmasked((byte)(0x80|11)); - _in.sendMask(); - _in.put("Hello World".getBytes(StringUtil.__UTF8)); - // System.err.println("tosend="+TypeUtil.toHexString(_in.asArray())); - - int progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals("Hello World",_handler._data.get(0)); - assertEquals(0x8,_handler._flags); - assertEquals(0x1,_handler._opcode); - assertTrue(_parser.isBufferEmpty()); - _parser.returnBuffer(); - assertTrue(_parser.getBuffer()==null); - } - - @Test - public void testShortUtf8() throws Exception - { - String string = "Hell\uFF4f W\uFF4Frld"; - byte[] bytes = string.getBytes("UTF-8"); - - _in.putUnmasked((byte)0x81); - _in.putUnmasked((byte)(0x80|bytes.length)); - _in.sendMask(); - _in.put(bytes); - - int progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals(string,_handler._data.get(0)); - assertEquals(0x8,_handler._flags); - assertEquals(0x1,_handler._opcode); - _parser.returnBuffer(); - assertTrue(_parser.isBufferEmpty()); - assertTrue(_parser.getBuffer()==null); - } - - @Test - public void testMediumText() throws Exception - { - String string = "Hell\uFF4f Medium W\uFF4Frld "; - for (int i=0;i<4;i++) - string = string+string; - string += ". The end."; - - byte[] bytes = string.getBytes(StringUtil.__UTF8); - - _in.putUnmasked((byte)0x81); - _in.putUnmasked((byte)(0x80|0x7E)); - _in.putUnmasked((byte)(bytes.length>>8)); - _in.putUnmasked((byte)(bytes.length&0xff)); - _in.sendMask(); - _in.put(bytes); - - int progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals(string,_handler._data.get(0)); - assertEquals(0x8,_handler._flags); - assertEquals(0x1,_handler._opcode); - _parser.returnBuffer(); - assertTrue(_parser.isBufferEmpty()); - assertTrue(_parser.getBuffer()==null); - } - - @Test - public void testLongText() throws Exception - { - WebSocketBuffers buffers = new WebSocketBuffers(0x20000); - ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - WebSocketParserRFC6455 parser=new WebSocketParserRFC6455(buffers, endPoint,_handler,false); - ByteArrayBuffer in = new ByteArrayBuffer(0x20000); - endPoint.setIn(in); - - String string = "Hell\uFF4f Big W\uFF4Frld "; - for (int i=0;i<12;i++) - string = string+string; - string += ". The end."; - - byte[] bytes = string.getBytes("UTF-8"); - - _in.sendMask(); - in.put((byte)0x84); - in.put((byte)0x7F); - in.put((byte)0x00); - in.put((byte)0x00); - in.put((byte)0x00); - in.put((byte)0x00); - in.put((byte)0x00); - in.put((byte)(bytes.length>>16)); - in.put((byte)((bytes.length>>8)&0xff)); - in.put((byte)(bytes.length&0xff)); - in.put(bytes); - - int progress =parser.parseNext(); - parser.returnBuffer(); - - assertTrue(progress>0); - assertEquals(string,_handler._data.get(0)); - assertTrue(parser.isBufferEmpty()); - assertTrue(parser.getBuffer()==null); - } - - @Test - public void testShortFragmentTest() throws Exception - { - _in.putUnmasked((byte)0x01); - _in.putUnmasked((byte)0x86); - _in.sendMask(); - _in.put("Hello ".getBytes(StringUtil.__UTF8)); - _in.putUnmasked((byte)0x80); - _in.putUnmasked((byte)0x85); - _in.sendMask(); - _in.put("World".getBytes(StringUtil.__UTF8)); - - int progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals(0,_handler._data.size()); - assertFalse(_parser.isBufferEmpty()); - assertFalse(_parser.getBuffer()==null); - - progress =_parser.parseNext(); - _parser.returnBuffer(); - - assertTrue(progress>0); - assertEquals("Hello World",_handler._data.get(0)); - assertTrue(_parser.isBufferEmpty()); - assertTrue(_parser.getBuffer()==null); - } - - @Test - public void testFrameTooLarge() throws Exception - { - // Buffers are only 1024, so this frame is too large - _parser.setFakeFragments(false); - - _in.putUnmasked((byte)0x81); - _in.putUnmasked((byte)(0x80|0x7E)); - _in.putUnmasked((byte)(2048>>8)); - _in.putUnmasked((byte)(2048&0xff)); - _in.sendMask(); - - int progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,_handler._code); - - - for (int i=0;i<2048;i++) - _in.put((byte)'a'); - progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals(0,_handler._data.size()); - assertEquals(0,_handler._utf8.length()); - - _handler._code=0; - - _in.putUnmasked((byte)0x81); - _in.putUnmasked((byte)0xFE); - _in.putUnmasked((byte)(1024>>8)); - _in.putUnmasked((byte)(1024&0xff)); - _in.sendMask(); - for (int i=0;i<1024;i++) - _in.put((byte)'a'); - - progress =_parser.parseNext(); - assertTrue(progress>0); - assertEquals(0,_handler._data.size()); - } - - @Test - public void testFakeFragement() throws Exception - { - // Buffers are only 1024, so this frame will be fake fragmented - _parser.setFakeFragments(true); - - _in.putUnmasked((byte)0x81); - _in.putUnmasked((byte)(0x80|0x7E)); - _in.putUnmasked((byte)(2048>>8)); - _in.putUnmasked((byte)(2048&0xff)); - _in.sendMask(); - for (int i=0;i<2048;i++) - _in.put((byte)('a'+i%26)); - - int progress =_parser.parseNext(); - assertTrue(progress>0); - - assertEquals(2,_handler._frames); - assertEquals(WebSocketConnectionRFC6455.OP_CONTINUATION,_handler._opcode); - assertEquals(1,_handler._data.size()); - String mesg=_handler._data.remove(0); - - assertEquals(2048,mesg.length()); - - for (int i=0;i<2048;i++) - assertEquals(('a'+i%26),mesg.charAt(i)); - } - - @Test - public void testClose() throws Exception - { - String string = "Game Over"; - byte[] bytes = string.getBytes("UTF-8"); - - _in.putUnmasked((byte)(0x80|0x08)); - _in.putUnmasked((byte)(0x80|(2+bytes.length))); - _in.sendMask(); - _in.put((byte)(1000/0x100)); - _in.put((byte)(1000%0x100)); - _in.put(bytes); - - int progress =_parser.parseNext(); - - assertTrue(progress>0); - assertEquals(string,_handler._data.get(0).substring(2)); - assertEquals(0x8,_handler._flags); - assertEquals(0x8,_handler._opcode); - _parser.returnBuffer(); - assertTrue(_parser.isBufferEmpty()); - assertTrue(_parser.getBuffer()==null); - - _in.clear(); - _in.put(bytes); - _endPoint.setIn(_in); - progress =_parser.parseNext(); - assertTrue(progress>0); - - _endPoint.shutdownInput(); - - progress =_parser.parseNext(); - assertEquals(-1,progress); - - } - - - private class Handler implements WebSocketParser.FrameHandler - { - Utf8StringBuilder _utf8 = new Utf8StringBuilder(); - public List _data = new ArrayList(); - private byte _flags; - private byte _opcode; - int _code; - int _frames; - - public void onFrame(byte flags, byte opcode, ByteBuffer buffer) - { - _frames++; - _flags=flags; - _opcode=opcode; - if ((flags&0x8)==0) - _utf8.append(buffer.array(),buffer.getIndex(),buffer.length()); - else if (_utf8.length()==0) - _data.add(buffer.toString("utf-8")); - else - { - _utf8.append(buffer.array(),buffer.getIndex(),buffer.length()); - _data.add(_utf8.toString()); - _utf8.reset(); - } - } - - public void close(int code,String message) - { - _code=code; - } - } - */ -} From b3a26517db7eb70054877f509e8d43b318a5e1a2 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 21 Jun 2012 11:03:30 -0700 Subject: [PATCH 3/8] WebSocketPolicy new fields and documentation updates --- .../jetty/websocket/api/WebSocketPolicy.java | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java index f6fd459d2f4..3aee99fc01a 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java @@ -7,15 +7,44 @@ import org.eclipse.jetty.websocket.masks.Masker; */ public class WebSocketPolicy { + public static WebSocketPolicy newClientPolicy() + { + return new WebSocketPolicy(WebSocketBehavior.CLIENT); + } + + public static WebSocketPolicy newServerPolicy() + { + return new WebSocketPolicy(WebSocketBehavior.SERVER); + } + /** - * The maximum size of a text message during parsing/generating + * The maximum size of a text message during parsing/generating. + *

+ * Default: 16384 (16 K) */ - private int maxTextMessageSize = -1; + private int maxTextMessageSize = 16384; + /** - * The maximum size of a binary message during parsing/generating + * The maximum size of a binary message during parsing/generating. + *

+ * Default: -1 (no validation) */ private int maxBinaryMessageSize = -1; + /** + * Buffer size, which is also the max frame byte size. + *

+ * Default: 65535 (64 K) + */ + private int bufferSize = 65535; + + /** + * The time in ms (milliseconds) that a websocket may be idle before closing. + *

+ * Default: 300000 (ms) + */ + private int maxIdleTime = 300000; + /** * The implementation for masking */ @@ -60,6 +89,11 @@ public class WebSocketPolicy return behavior; } + public int getBufferSize() + { + return bufferSize; + } + public Masker getMasker() { return masker; @@ -70,11 +104,21 @@ public class WebSocketPolicy return maxBinaryMessageSize; } + public int getMaxIdleTime() + { + return maxIdleTime; + } + public int getMaxTextMessageSize() { return maxTextMessageSize; } + public void setBufferSize(int bufferSize) + { + this.bufferSize = bufferSize; + } + public void setMasker(Masker masker) { this.masker = masker; @@ -85,6 +129,11 @@ public class WebSocketPolicy this.maxBinaryMessageSize = maxBinaryMessageSize; } + public void setMaxIdleTime(int maxIdleTime) + { + this.maxIdleTime = maxIdleTime; + } + public void setMaxTextMessageSize(int maxTextMessageSize) { this.maxTextMessageSize = maxTextMessageSize; From dd668f7d8a3b0d30642ad4919f662970c237e353 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 21 Jun 2012 11:03:51 -0700 Subject: [PATCH 4/8] Adding TextFrame(String) constructor --- .../eclipse/jetty/websocket/frames/TextFrame.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/TextFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/TextFrame.java index bda09c806e9..fcf708b8aec 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/TextFrame.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/TextFrame.java @@ -17,6 +17,18 @@ public class TextFrame extends DataFrame super(OpCode.TEXT); } + /** + * Construct text frame with message data + * + * @param message + * the message data + */ + public TextFrame(String message) + { + this(); + setData(message); + } + /** * Get the data * From 37249d09db9723503ba7390e8644044f5498ce1d Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 21 Jun 2012 11:04:13 -0700 Subject: [PATCH 5/8] Adding TEXT & BINARY to root generator map --- .../java/org/eclipse/jetty/websocket/generator/Generator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/generator/Generator.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/generator/Generator.java index fc00c32c5a1..b236efc0106 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/generator/Generator.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/generator/Generator.java @@ -38,6 +38,8 @@ public class Generator { public Generator(ByteBufferPool bufferPool, WebSocketPolicy policy) { + generators.put(OpCode.BINARY,new BinaryFrameGenerator(bufferPool,policy)); + generators.put(OpCode.TEXT,new TextFrameGenerator(bufferPool,policy)); generators.put(OpCode.PING,new PingFrameGenerator(bufferPool,policy)); generators.put(OpCode.PONG,new PongFrameGenerator(bufferPool,policy)); generators.put(OpCode.CLOSE,new CloseFrameGenerator(bufferPool,policy)); From 8e21e14c6dd1b5b4b8d9c50f8281e11ccbe38699 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 21 Jun 2012 11:04:44 -0700 Subject: [PATCH 6/8] Minor documentation updates --- .../java/org/eclipse/jetty/websocket/parser/FrameParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/FrameParser.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/FrameParser.java index 7f9b5cfe726..b9a0b260678 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/FrameParser.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/parser/FrameParser.java @@ -70,7 +70,7 @@ public abstract class FrameParser int amt = Math.min(length,src.remaining()); if (getFrame().isMasked()) { - // Demask the content + // Demask the content 1 byte at a time byte mask[] = getFrame().getMask(); for (int i = 0; i < amt; i++) { @@ -80,6 +80,7 @@ public abstract class FrameParser else { // Copy the content as-is + // TODO: Look into having a BufferUtil.put(from,to,len) method byte b[] = new byte[amt]; src.get(b,0,amt); dest.put(b,0,amt); From ec51481c022dd141f91cb0931046da3dad74c788 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 21 Jun 2012 11:05:20 -0700 Subject: [PATCH 7/8] Adding Generator to Parser roundtrip test cases (refactored from old codebase) --- .../GeneratorParserRoundtripTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/GeneratorParserRoundtripTest.java diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/GeneratorParserRoundtripTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/GeneratorParserRoundtripTest.java new file mode 100644 index 00000000000..bca799dbe1e --- /dev/null +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/GeneratorParserRoundtripTest.java @@ -0,0 +1,87 @@ +package org.eclipse.jetty.websocket; + +import static org.hamcrest.Matchers.*; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.io.StandardByteBufferPool; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.frames.TextFrame; +import org.eclipse.jetty.websocket.generator.Generator; +import org.eclipse.jetty.websocket.generator.TextFrameGenerator; +import org.eclipse.jetty.websocket.masks.RandomMasker; +import org.eclipse.jetty.websocket.parser.FrameParseCapture; +import org.eclipse.jetty.websocket.parser.Parser; +import org.eclipse.jetty.websocket.parser.TextPayloadParser; +import org.junit.Assert; +import org.junit.Test; + +public class GeneratorParserRoundtripTest +{ + @Test + public void testParserAndGenerator() throws Exception + { + Debug.enableDebugLogging(Generator.class); + Debug.enableDebugLogging(TextFrameGenerator.class); + Debug.enableDebugLogging(Parser.class); + Debug.enableDebugLogging(TextPayloadParser.class); + + WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); + StandardByteBufferPool bufferPool = new StandardByteBufferPool(); + Generator gen = new Generator(bufferPool,policy); + Parser parser = new Parser(policy); + + // Generate Buffer + String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + ByteBuffer out = gen.generate(new TextFrame(message)); + Debug.dumpState(out); + + // Parse Buffer + FrameParseCapture capture = new FrameParseCapture(); + parser.addListener(capture); + out.flip(); + parser.parse(out); + + // Validate + capture.assertNoErrors(); + capture.assertHasFrame(TextFrame.class,1); + + TextFrame txt = (TextFrame)capture.getFrames().get(0); + Assert.assertThat("Text parsed",txt.getData().toString(),is(message)); + } + + @Test + public void testParserAndGeneratorMasked() throws Exception + { + Debug.enableDebugLogging(Generator.class); + Debug.enableDebugLogging(TextFrameGenerator.class); + Debug.enableDebugLogging(Parser.class); + Debug.enableDebugLogging(TextPayloadParser.class); + + WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); + policy.setMasker(new RandomMasker()); + + StandardByteBufferPool bufferPool = new StandardByteBufferPool(); + Generator gen = new Generator(bufferPool,policy); + Parser parser = new Parser(policy); + + // Generate Buffer + String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + ByteBuffer out = gen.generate(new TextFrame(message)); + Debug.dumpState(out); + + // Parse Buffer + FrameParseCapture capture = new FrameParseCapture(); + parser.addListener(capture); + out.flip(); + parser.parse(out); + + // Validate + capture.assertNoErrors(); + capture.assertHasFrame(TextFrame.class,1); + + TextFrame txt = (TextFrame)capture.getFrames().get(0); + Assert.assertTrue("Text.isMasked",txt.isMasked()); + Assert.assertThat("Text parsed",txt.getData().toString(),is(message)); + } +} From dc68d30b4b0b64695002f1bdacf6071aefbf717f Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 21 Jun 2012 11:11:04 -0700 Subject: [PATCH 8/8] Continuing refactoring of websocket-server by commenting out no longer available classes (no more red) --- .../server/AsyncWebSocketConnection.java | 29 + .../websocket/server/WebSocketHandler.java | 43 +- ...ctory.java => WebSocketServerFactory.java} | 381 ++-- .../websocket/server/WebSocketServlet.java | 95 +- .../server/WebSocketServletConnection.java | 4 +- .../WebSocketServletConnectionRFC6455.java | 25 +- .../jetty/websocket/server/TestServer.java | 724 ++++--- .../websocket/server/WebSocketCommTest.java | 33 +- .../server/WebSocketLoadRFC6455Test.java | 294 +-- .../server/WebSocketMessageRFC6455Test.java | 1898 ++++++++--------- .../server/WebSocketOverSSLTest.java | 223 +- .../server/WebSocketRedeployTest.java | 110 +- .../server/WebSocketServletRFCTest.java | 154 +- .../websocket/server/helper/SafariD00.java | 21 +- .../helper/WebSocketCaptureServlet.java | 5 +- 15 files changed, 2033 insertions(+), 2006 deletions(-) create mode 100644 jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/AsyncWebSocketConnection.java rename jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/{WebSocketFactory.java => WebSocketServerFactory.java} (69%) diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/AsyncWebSocketConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/AsyncWebSocketConnection.java new file mode 100644 index 00000000000..232fc0f56ca --- /dev/null +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/AsyncWebSocketConnection.java @@ -0,0 +1,29 @@ +package org.eclipse.jetty.websocket.server; + +import java.util.concurrent.Executor; + +import org.eclipse.jetty.io.AbstractAsyncConnection; +import org.eclipse.jetty.io.AsyncEndPoint; + +public class AsyncWebSocketConnection extends AbstractAsyncConnection +{ + // TODO: track extensions? (only those that need to operate at this level?) + // TODO: track generic WebSocket.Connection (for API access)? + + public AsyncWebSocketConnection(AsyncEndPoint endp, Executor executor) + { + super(endp,executor); + } + + public AsyncWebSocketConnection(AsyncEndPoint endp, Executor executor, boolean executeOnlyFailure) + { + super(endp,executor,executeOnlyFailure); + } + + @Override + public void onFillable() + { + // TODO Auto-generated method stub + + } +} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java index e8d3273054f..1ede4f46c60 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java @@ -19,11 +19,11 @@ // 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 +// 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. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.websocket.server; @@ -36,30 +36,37 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.websocket.server.WebSocketFactory.Acceptor; +import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; -public abstract class WebSocketHandler extends HandlerWrapper implements WebSocketFactory.Acceptor +public abstract class WebSocketHandler extends HandlerWrapper implements WebSocketServerFactory.Acceptor { - private final WebSocketFactory _webSocketFactory=new WebSocketFactory(this,32*1024); - - public WebSocketFactory getWebSocketFactory() + private final WebSocketServerFactory webSocketFactory; + + public WebSocketHandler() { - return _webSocketFactory; + WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); + webSocketFactory = new WebSocketServerFactory(this,policy); } - /* ------------------------------------------------------------ */ @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted()) - return; - super.handle(target,baseRequest,request,response); - } - - /* ------------------------------------------------------------ */ public boolean checkOrigin(HttpServletRequest request, String origin) { return true; } - + + public WebSocketServerFactory getWebSocketFactory() + { + return webSocketFactory; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (webSocketFactory.acceptWebSocket(request,response) || response.isCommitted()) + { + return; + } + super.handle(target,baseRequest,request,response); + } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java similarity index 69% rename from jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketFactory.java rename to jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java index 9b94e063e5c..cf75a85dd1f 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketFactory.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java @@ -21,17 +21,19 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketConnectionRFC6455; -import org.eclipse.jetty.websocket.WebSocket.Connection; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.extensions.Extension; import org.eclipse.jetty.websocket.extensions.deflate.DeflateFrameExtension; import org.eclipse.jetty.websocket.extensions.fragment.FragmentExtension; @@ -40,13 +42,19 @@ import org.eclipse.jetty.websocket.extensions.identity.IdentityExtension; /** * Factory to create WebSocket connections */ -public class WebSocketFactory extends AbstractLifeCycle +public class WebSocketServerFactory extends AbstractLifeCycle { - private static final Logger LOG = Log.getLogger(WebSocketFactory.class); - private final Queue connections = new ConcurrentLinkedQueue(); - public interface Acceptor { + /* ------------------------------------------------------------ */ + /** + *

Checks the origin of an incoming WebSocket handshake request.

+ * @param request the incoming HTTP upgrade request + * @param origin the origin URI + * @return boolean to indicate that the origin is acceptable. + */ + boolean checkOrigin(HttpServletRequest request, String origin); + /* ------------------------------------------------------------ */ /** *

Factory method that applications needs to implement to return a @@ -56,16 +64,10 @@ public class WebSocketFactory extends AbstractLifeCycle * @return a new {@link WebSocket} object that will handle websocket events. */ WebSocket doWebSocketConnect(HttpServletRequest request, String protocol); - - /* ------------------------------------------------------------ */ - /** - *

Checks the origin of an incoming WebSocket handshake request.

- * @param request the incoming HTTP upgrade request - * @param origin the origin URI - * @return boolean to indicate that the origin is acceptable. - */ - boolean checkOrigin(HttpServletRequest request, String origin); } + private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class); + + private final Queue connections = new ConcurrentLinkedQueue(); private final Map> _extensionClasses = new HashMap>(); { @@ -75,204 +77,12 @@ public class WebSocketFactory extends AbstractLifeCycle } private final Acceptor _acceptor; - private WebSocketBuffers _buffers; - private int _maxIdleTime = 300000; - private int _maxTextMessageSize = 16 * 1024; - private int _maxBinaryMessageSize = -1; + private WebSocketPolicy policy; - public WebSocketFactory(Acceptor acceptor) + public WebSocketServerFactory(Acceptor acceptor, WebSocketPolicy policy) { - this(acceptor, 64 * 1024); - } - - public WebSocketFactory(Acceptor acceptor, int bufferSize) - { - _buffers = new WebSocketBuffers(bufferSize); - _acceptor = acceptor; - } - - /** - * @return A modifiable map of extension name to extension class - */ - public Map> getExtensionClassesMap() - { - return _extensionClasses; - } - - /** - * Get the maxIdleTime. - * - * @return the maxIdleTime - */ - public long getMaxIdleTime() - { - return _maxIdleTime; - } - - /** - * Set the maxIdleTime. - * - * @param maxIdleTime the maxIdleTime to set - */ - public void setMaxIdleTime(int maxIdleTime) - { - _maxIdleTime = maxIdleTime; - } - - /** - * Get the bufferSize. - * - * @return the bufferSize - */ - public int getBufferSize() - { - return _buffers.getBufferSize(); - } - - /** - * Set the bufferSize. - * - * @param bufferSize the bufferSize to set - */ - public void setBufferSize(int bufferSize) - { - if (bufferSize != getBufferSize()) - _buffers = new WebSocketBuffers(bufferSize); - } - - /** - * @return The initial maximum text message size (in characters) for a connection - */ - public int getMaxTextMessageSize() - { - return _maxTextMessageSize; - } - - /** - * Set the initial maximum text message size for a connection. This can be changed by - * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}. - * @param maxTextMessageSize The default maximum text message size (in characters) for a connection - */ - public void setMaxTextMessageSize(int maxTextMessageSize) - { - _maxTextMessageSize = maxTextMessageSize; - } - - /** - * @return The initial maximum binary message size (in bytes) for a connection - */ - public int getMaxBinaryMessageSize() - { - return _maxBinaryMessageSize; - } - - /** - * Set the initial maximum binary message size for a connection. This can be changed by - * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}. - * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection - */ - public void setMaxBinaryMessageSize(int maxBinaryMessageSize) - { - _maxBinaryMessageSize = maxBinaryMessageSize; - } - - @Override - protected void doStop() throws Exception - { - closeConnections(); - } - - /** - * Upgrade the request/response to a WebSocket Connection. - *

This method will not normally return, but will instead throw a - * UpgradeConnectionException, to exit HTTP handling and initiate - * WebSocket handling of the connection. - * - * @param request The request to upgrade - * @param response The response to upgrade - * @param websocket The websocket handler implementation to use - * @param protocol The websocket protocol - * @throws IOException in case of I/O errors - */ - public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String protocol) - throws IOException - { - if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) - throw new IllegalStateException("!Upgrade:websocket"); - if (!"HTTP/1.1".equals(request.getProtocol())) - throw new IllegalStateException("!HTTP/1.1"); - - int draft = request.getIntHeader("Sec-WebSocket-Version"); - if (draft < 0) { - // Old pre-RFC version specifications (header not present in RFC-6455) - draft = request.getIntHeader("Sec-WebSocket-Draft"); - } - AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection(); - if (http instanceof BlockingHttpConnection) - throw new IllegalStateException("Websockets not supported on blocking connectors"); - ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint(); - - List extensions_requested = new ArrayList(); - @SuppressWarnings("unchecked") - Enumeration e = request.getHeaders("Sec-WebSocket-Extensions"); - while (e.hasMoreElements()) - { - QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),","); - while (tok.hasMoreTokens()) - { - extensions_requested.add(tok.nextToken()); - } - } - - final WebSocketServletConnection connection; - switch (draft) - { - case WebSocketConnectionRFC6455.VERSION: // RFC 6455 Version - { - List extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, 3); - connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft); - break; - } - default: - { - LOG.warn("Unsupported Websocket version: " + draft); - // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol - // Using the examples as outlined - response.setHeader("Sec-WebSocket-Version", "13, 8, 6, 0"); - throw new HttpException(400, "Unsupported websocket version specification: " + draft); - } - } - - addConnection(connection); - - // Set the defaults - connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize); - connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize); - - // Let the connection finish processing the handshake - connection.handshake(request, response, protocol); - response.flushBuffer(); - - // Give the connection any unused data from the HTTP connection. - connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer()); - connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer()); - - // Tell jetty about the new connection - LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection); - request.setAttribute("org.eclipse.jetty.io.Connection", connection); - } - - protected String[] parseProtocols(String protocol) - { - if (protocol == null) - return new String[]{null}; - protocol = protocol.trim(); - if (protocol == null || protocol.length() == 0) - return new String[]{null}; - String[] passed = protocol.split("\\s*,\\s*"); - String[] protocols = new String[passed.length + 1]; - System.arraycopy(passed, 0, protocols, 0, passed.length); - return protocols; + this._acceptor = acceptor; + this.policy = policy; } public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) @@ -282,7 +92,9 @@ public class WebSocketFactory extends AbstractLifeCycle { String origin = request.getHeader("Origin"); if (origin==null) + { origin = request.getHeader("Sec-WebSocket-Origin"); + } if (!_acceptor.checkOrigin(request,origin)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); @@ -295,7 +107,7 @@ public class WebSocketFactory extends AbstractLifeCycle @SuppressWarnings("unchecked") Enumeration protocols = request.getHeaders("Sec-WebSocket-Protocol"); String protocol=null; - while (protocol==null && protocols!=null && protocols.hasMoreElements()) + while ((protocol==null) && (protocols!=null) && protocols.hasMoreElements()) { String candidate = protocols.nextElement(); for (String p : parseProtocols(candidate)) @@ -329,6 +141,43 @@ public class WebSocketFactory extends AbstractLifeCycle return false; } + + protected boolean addConnection(WebSocketServletConnection connection) + { + return isRunning() && connections.add(connection); + } + + protected void closeConnections() + { + for (WebSocketServletConnection connection : connections) + { + // TODO connection.shutdown(); + } + } + + @Override + protected void doStop() throws Exception + { + closeConnections(); + } + + /** + * @return A modifiable map of extension name to extension class + */ + public Map> getExtensionClassesMap() + { + return _extensionClasses; + } + + /** + * Get the policy in use for WebSockets. + * + * @return + */ + public WebSocketPolicy getPolicy() + { + return policy; + } public List initExtensions(List requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits) { @@ -349,7 +198,9 @@ public class WebSocketFactory extends AbstractLifeCycle Extension extension = newExtension(extName); if (extension==null) + { continue; + } if (extension.init(parameters)) { @@ -367,7 +218,9 @@ public class WebSocketFactory extends AbstractLifeCycle { Class extClass = _extensionClasses.get(name); if (extClass!=null) + { return extClass.newInstance(); + } } catch (Exception e) { @@ -377,9 +230,21 @@ public class WebSocketFactory extends AbstractLifeCycle return null; } - protected boolean addConnection(WebSocketServletConnection connection) + protected String[] parseProtocols(String protocol) { - return isRunning() && connections.add(connection); + if (protocol == null) + { + return new String[]{null}; + } + protocol = protocol.trim(); + if ((protocol == null) || (protocol.length() == 0)) + { + return new String[]{null}; + } + String[] passed = protocol.split("\\s*,\\s*"); + String[] protocols = new String[passed.length + 1]; + System.arraycopy(passed, 0, protocols, 0, passed.length); + return protocols; } protected boolean removeConnection(WebSocketServletConnection connection) @@ -387,9 +252,89 @@ public class WebSocketFactory extends AbstractLifeCycle return connections.remove(connection); } - protected void closeConnections() + /** + * Upgrade the request/response to a WebSocket Connection. + *

This method will not normally return, but will instead throw a + * UpgradeConnectionException, to exit HTTP handling and initiate + * WebSocket handling of the connection. + * + * @param request The request to upgrade + * @param response The response to upgrade + * @param websocket The websocket handler implementation to use + * @param protocol The websocket protocol + * @throws IOException in case of I/O errors + */ + public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String protocol) + throws IOException { - for (WebSocketServletConnection connection : connections) - connection.shutdown(); + if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) + { + throw new IllegalStateException("!Upgrade:websocket"); + } + if (!"HTTP/1.1".equals(request.getProtocol())) + { + throw new IllegalStateException("!HTTP/1.1"); + } + + int draft = request.getIntHeader("Sec-WebSocket-Version"); + if (draft < 0) { + // Old pre-RFC version specifications (header not present in RFC-6455) + draft = request.getIntHeader("Sec-WebSocket-Draft"); + } + HttpConnection http = HttpConnection.getCurrentConnection(); + AsyncEndPoint endp = http.getEndPoint(); + + List extensions_requested = new ArrayList(); + @SuppressWarnings("unchecked") + Enumeration e = request.getHeaders("Sec-WebSocket-Extensions"); + while (e.hasMoreElements()) + { + QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),","); + while (tok.hasMoreTokens()) + { + extensions_requested.add(tok.nextToken()); + } + } + + final WebSocketServletConnection connection; + switch (draft) + { + case org.eclipse.jetty.websocket.api.WebSocket.VERSION: // RFC 6455 Version + { +// List extensions = initExtensions(extensions_requested, +// 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, +// 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, +// 3); +// connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft); + break; + } + default: + { + LOG.warn("Unsupported Websocket version: " + draft); + // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol + // Using the examples as outlined + response.setHeader("Sec-WebSocket-Version", "" + org.eclipse.jetty.websocket.api.WebSocket.VERSION /*+ ", 0"*/); + response.setStatus(HttpStatus.BAD_REQUEST_400); + return; + } + } + + // addConnection(connection); + + // Set the defaults + // connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize); + // connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize); + + // Let the connection finish processing the handshake + // connection.handshake(request, response, protocol); + response.flushBuffer(); + + // Give the connection any unused data from the HTTP connection. + // connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer()); + // connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer()); + + // Tell jetty about the new connection + // LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection); + // request.setAttribute("org.eclipse.jetty.io.Connection", connection); } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServlet.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServlet.java index bb26adca609..c70e9461118 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServlet.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServlet.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011 Intalio, Inc. + * Copyright (c) 2011 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 @@ -13,22 +13,11 @@ * * You may elect to redistribute this code under either of these licenses. *******************************************************************************/ -// ======================================================================== -// 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.server; import java.io.IOException; + import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -36,9 +25,9 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.server.WebSocketFactory.Acceptor; +import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; -/* ------------------------------------------------------------ */ /** * Servlet to upgrade connections to WebSocket *

@@ -58,12 +47,30 @@ import org.eclipse.jetty.websocket.server.WebSocketFactory.Acceptor; * that a websocket may be accept before closing. */ @SuppressWarnings("serial") -public abstract class WebSocketServlet extends HttpServlet implements WebSocketFactory.Acceptor +public abstract class WebSocketServlet extends HttpServlet implements WebSocketServerFactory.Acceptor { private final Logger LOG = Log.getLogger(getClass()); - private WebSocketFactory _webSocketFactory; + private WebSocketServerFactory webSocketFactory; + + @Override + public boolean checkOrigin(HttpServletRequest request, String origin) + { + return true; + } + + @Override + public void destroy() + { + try + { + webSocketFactory.stop(); + } + catch (Exception x) + { + LOG.ignore(x); + } + } - /* ------------------------------------------------------------ */ /** * @see javax.servlet.GenericServlet#init() */ @@ -73,24 +80,27 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF try { String bs = getInitParameter("bufferSize"); - _webSocketFactory = new WebSocketFactory(this, bs == null ? 8192 : Integer.parseInt(bs)); - _webSocketFactory.start(); + WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); + if(bs != null) { + policy.setBufferSize(Integer.parseInt(bs)); + } String max = getInitParameter("maxIdleTime"); - if (max != null) - _webSocketFactory.setMaxIdleTime(Integer.parseInt(max)); + if (max != null) { + policy.setMaxIdleTime(Integer.parseInt(max)); + } max = getInitParameter("maxTextMessageSize"); - if (max != null) - _webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max)); + if (max != null) { + policy.setMaxTextMessageSize(Integer.parseInt(max)); + } max = getInitParameter("maxBinaryMessageSize"); - if (max != null) - _webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max)); - } - catch (ServletException x) - { - throw x; + if (max != null) { + policy.setMaxBinaryMessageSize(Integer.parseInt(max)); + } + + webSocketFactory = new WebSocketServerFactory(this,policy); } catch (Exception x) { @@ -98,35 +108,16 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF } } - /* ------------------------------------------------------------ */ /** * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - if (_webSocketFactory.acceptWebSocket(request, response) || response.isCommitted()) + if (webSocketFactory.acceptWebSocket(request,response) || response.isCommitted()) + { return; + } super.service(request, response); } - - /* ------------------------------------------------------------ */ - public boolean checkOrigin(HttpServletRequest request, String origin) - { - return true; - } - - /* ------------------------------------------------------------ */ - @Override - public void destroy() - { - try - { - _webSocketFactory.stop(); - } - catch (Exception x) - { - LOG.ignore(x); - } - } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnection.java index 2f1c9449681..362637a1aac 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnection.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnection.java @@ -16,12 +16,12 @@ package org.eclipse.jetty.websocket.server; import java.io.IOException; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.websocket.WebSocketConnection; -public interface WebSocketServletConnection extends WebSocketConnection +public interface WebSocketServletConnection /* extends WebSocketConnection */ { void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException; } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnectionRFC6455.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnectionRFC6455.java index 2b61a1c44f7..025396a6b43 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnectionRFC6455.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServletConnectionRFC6455.java @@ -16,25 +16,22 @@ package org.eclipse.jetty.websocket.server; import java.io.IOException; -import java.util.List; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketConnectionRFC6455; -import org.eclipse.jetty.websocket.extensions.Extension; - -public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection +public class WebSocketServletConnectionRFC6455 /* extends WebSocketConnectionRFC6455 implements WebSocketServletConnection */ { - private final WebSocketFactory factory; + private /* final */ WebSocketServerFactory factory; - public WebSocketServletConnectionRFC6455(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, + /* + public WebSocketServletConnectionRFC6455(WebSocketServerFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions, int draft) throws IOException { super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft); this.factory = factory; } + */ /* ------------------------------------------------------------ */ public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException @@ -43,27 +40,31 @@ public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC645 response.setHeader("Upgrade","WebSocket"); response.addHeader("Connection","Upgrade"); - response.addHeader("Sec-WebSocket-Accept",hashKey(key)); + // response.addHeader("Sec-WebSocket-Accept",hashKey(key)); if (subprotocol != null) { response.addHeader("Sec-WebSocket-Protocol",subprotocol); } + /* for (Extension ext : getExtensions()) { response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName()); } + */ response.sendError(101); - onFrameHandshake(); - onWebSocketOpen(); + // onFrameHandshake(); + // onWebSocketOpen(); } + /* @Override public void onClose() { super.onClose(); factory.removeConnection(this); } + */ } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TestServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TestServer.java index e1ba806eafd..f79a2aca42b 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TestServer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/TestServer.java @@ -22,34 +22,385 @@ import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.SelectChannelConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketConnectionRFC6455; -import org.eclipse.jetty.websocket.WebSocket.Connection; -import org.eclipse.jetty.websocket.WebSocket.FrameConnection; -import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage; -import org.eclipse.jetty.websocket.WebSocket.OnControl; -import org.eclipse.jetty.websocket.WebSocket.OnFrame; -import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; +import org.eclipse.jetty.websocket.api.OpCode; public class TestServer extends Server { + class TestEchoAssembleWebSocket extends TestWebSocket + { + + @Override + public void onMessage(byte[] data, int offset, int length) + { + super.onMessage(data,offset,length); + try + { + getConnection().sendMessage(data,offset,length); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + @Override + public void onMessage(final String data) + { + super.onMessage(data); + try + { + getConnection().sendMessage(data); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + @Override + public void onOpen(Connection connection) + { + super.onOpen(connection); + connection.setMaxTextMessageSize(64*1024); + connection.setMaxBinaryMessageSize(64*1024); + } + } + + class TestEchoBroadcastPingWebSocket extends TestEchoBroadcastWebSocket + { + Thread _keepAlive; // A dedicated thread is not a good way to do this + CountDownLatch _latch = new CountDownLatch(1); + + @Override + public void onClose(int code, String message) + { + _latch.countDown(); + super.onClose(code,message); + } + + + @Override + public boolean onControl(byte controlCode, byte[] data, int offset, int length) + { + if (controlCode==OpCode.PONG.getCode()) + { + System.err.println("Pong "+getConnection()); + } + return super.onControl(controlCode,data,offset,length); + } + + + @Override + public void onHandshake(final FrameConnection connection) + { + super.onHandshake(connection); + _keepAlive=new Thread() + { + @Override + public void run() + { + try + { + while(!_latch.await(10,TimeUnit.SECONDS)) + { + System.err.println("Ping "+connection); + byte[] data = { (byte)1, (byte) 2, (byte) 3 }; + connection.sendControl(OpCode.PING.getCode(),data,0,data.length); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }; + _keepAlive.start(); + } + + + } + + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class TestEchoBroadcastWebSocket extends TestWebSocket + { + @Override + public void onClose(int code,String message) + { + super.onClose(code,message); + _broadcast.remove(this); + } + + @Override + public void onMessage(byte[] data, int offset, int length) + { + super.onMessage(data,offset,length); + for (TestWebSocket ws : _broadcast) + { + try + { + ws.getConnection().sendMessage(data,offset,length); + } + catch (IOException e) + { + _broadcast.remove(ws); + e.printStackTrace(); + } + } + } + + @Override + public void onMessage(final String data) + { + super.onMessage(data); + for (TestWebSocket ws : _broadcast) + { + try + { + ws.getConnection().sendMessage(data); + } + catch (IOException e) + { + _broadcast.remove(ws); + e.printStackTrace(); + } + } + } + + @Override + public void onOpen(Connection connection) + { + super.onOpen(connection); + _broadcast.add(this); + } + } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class TestEchoFragmentWebSocket extends TestWebSocket + { + @Override + public void onMessage(byte[] data, int offset, int length) + { + super.onMessage(data,offset,length); + try + { + getConnection().sendFrame((byte)0x0,getConnection().binaryOpcode(),data,offset,length/2); + getConnection().sendFrame((byte)0x8,getConnection().binaryOpcode(),data,offset+(length/2),length-(length/2)); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + @Override + public void onMessage(final String message) + { + super.onMessage(message); + try + { + byte[] data = message.getBytes(StringUtil.__UTF8); + int offset=0; + int length=data.length; + getConnection().sendFrame((byte)0x0,getConnection().textOpcode(),data,offset,length/2); + getConnection().sendFrame((byte)0x8,getConnection().textOpcode(),data,offset+(length/2),length-(length/2)); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + @Override + public void onOpen(Connection connection) + { + super.onOpen(connection); + connection.setMaxTextMessageSize(64*1024); + connection.setMaxBinaryMessageSize(64*1024); + } + } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class TestEchoWebSocket extends TestWebSocket + { + @Override + public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) + { + super.onFrame(flags,opcode,data,offset,length); + try + { + if (!getConnection().isControl(opcode)) + { + getConnection().sendFrame(flags,opcode,data,offset,length); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + + return false; + } + + @Override + public void onOpen(Connection connection) + { + super.onOpen(connection); + connection.setMaxTextMessageSize(-1); + connection.setMaxBinaryMessageSize(-1); + } + } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class TestWebSocket implements WebSocket, WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage, WebSocket.OnControl + { + protected FrameConnection _connection; + + public FrameConnection getConnection() + { + return _connection; + } + + @Override + public void onClose(int code,String message) + { + if (_verbose) + { + System.err.printf("%s#onDisonnect %d %s\n",this.getClass().getSimpleName(),code,message); + } + } + + @Override + public boolean onControl(byte controlCode, byte[] data, int offset, int length) + { + if (_verbose) + { + System.err.printf("%s#onControl %s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(controlCode),TypeUtil.toHexString(data,offset,length)); + } + return false; + } + + @Override + public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) + { + if (_verbose) + { + System.err.printf("%s#onFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),TypeUtil.toHexString(data,offset,length)); + } + return false; + } + + @Override + public void onHandshake(FrameConnection connection) + { + if (_verbose) + { + System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName()); + } + _connection = connection; + } + + @Override + public void onMessage(byte[] data, int offset, int length) + { + if (_verbose) + { + System.err.printf("%s#onMessage %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(data,offset,length)); + } + } + + @Override + public void onMessage(String data) + { + if (_verbose) + { + System.err.printf("%s#onMessage %s\n",this.getClass().getSimpleName(),data); + } + } + + @Override + public void onOpen(Connection connection) + { + if (_verbose) + { + System.err.printf("%s#onOpen %s %s\n",this.getClass().getSimpleName(),connection,connection.getProtocol()); + } + } + } private static final Logger LOG = Log.getLogger(TestServer.class); + public static void main(String... args) + { + try + { + int port=8080; + boolean verbose=false; + String docroot="src/test/webapp"; + + for (int i=0;i _broadcast = new ConcurrentLinkedQueue(); + SelectChannelConnector _connector; + + WebSocketHandler _wsHandler; + + ResourceHandler _rHandler; + + ConcurrentLinkedQueue _broadcast = new ConcurrentLinkedQueue(); + + public TestServer(int port) { _connector = new SelectChannelConnector(); @@ -58,6 +409,7 @@ public class TestServer extends Server addConnector(_connector); _wsHandler = new WebSocketHandler() { + @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { if ("org.ietf.websocket.test-echo".equals(protocol) || "echo".equals(protocol) || "lws-mirror-protocol".equals(protocol)) @@ -96,25 +448,7 @@ public class TestServer extends Server _wsHandler.setHandler(_rHandler); } - - /* ------------------------------------------------------------ */ - public boolean isVerbose() - { - return _verbose; - } - - /* ------------------------------------------------------------ */ - public void setVerbose(boolean verbose) - { - _verbose = verbose; - } - - /* ------------------------------------------------------------ */ - public void setResourceBase(String dir) - { - _rHandler.setResourceBase(dir); - } - + /* ------------------------------------------------------------ */ public String getResourceBase() { @@ -122,331 +456,21 @@ public class TestServer extends Server } /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - class TestWebSocket implements WebSocket, WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage, WebSocket.OnControl + public boolean isVerbose() { - protected FrameConnection _connection; - - public FrameConnection getConnection() - { - return _connection; - } - - public void onOpen(Connection connection) - { - if (_verbose) - System.err.printf("%s#onOpen %s %s\n",this.getClass().getSimpleName(),connection,connection.getProtocol()); - } - - public void onHandshake(FrameConnection connection) - { - if (_verbose) - System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName()); - _connection = connection; - } + return _verbose; + } - public void onClose(int code,String message) - { - if (_verbose) - System.err.printf("%s#onDisonnect %d %s\n",this.getClass().getSimpleName(),code,message); - } - - public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) - { - if (_verbose) - System.err.printf("%s#onFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),TypeUtil.toHexString(data,offset,length)); - return false; - } - - public boolean onControl(byte controlCode, byte[] data, int offset, int length) - { - if (_verbose) - System.err.printf("%s#onControl %s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(controlCode),TypeUtil.toHexString(data,offset,length)); - return false; - } - - public void onMessage(String data) - { - if (_verbose) - System.err.printf("%s#onMessage %s\n",this.getClass().getSimpleName(),data); - } - - public void onMessage(byte[] data, int offset, int length) - { - if (_verbose) - System.err.printf("%s#onMessage %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(data,offset,length)); - } + /* ------------------------------------------------------------ */ + public void setResourceBase(String dir) + { + _rHandler.setResourceBase(dir); } /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - class TestEchoWebSocket extends TestWebSocket + public void setVerbose(boolean verbose) { - @Override - public void onOpen(Connection connection) - { - super.onOpen(connection); - connection.setMaxTextMessageSize(-1); - connection.setMaxBinaryMessageSize(-1); - } - - @Override - public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) - { - super.onFrame(flags,opcode,data,offset,length); - try - { - if (!getConnection().isControl(opcode)) - getConnection().sendFrame(flags,opcode,data,offset,length); - } - catch (IOException e) - { - e.printStackTrace(); - } - - return false; - } - } - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - class TestEchoBroadcastPingWebSocket extends TestEchoBroadcastWebSocket - { - Thread _keepAlive; // A dedicated thread is not a good way to do this - CountDownLatch _latch = new CountDownLatch(1); - - @Override - public void onHandshake(final FrameConnection connection) - { - super.onHandshake(connection); - _keepAlive=new Thread() - { - @Override - public void run() - { - try - { - while(!_latch.await(10,TimeUnit.SECONDS)) - { - System.err.println("Ping "+connection); - byte[] data = { (byte)1, (byte) 2, (byte) 3 }; - connection.sendControl(WebSocketConnectionRFC6455.OP_PING,data,0,data.length); - } - } - catch (Exception e) - { - e.printStackTrace(); - } - } - }; - _keepAlive.start(); - } - - - @Override - public boolean onControl(byte controlCode, byte[] data, int offset, int length) - { - if (controlCode==WebSocketConnectionRFC6455.OP_PONG) - System.err.println("Pong "+getConnection()); - return super.onControl(controlCode,data,offset,length); - } - - - @Override - public void onClose(int code, String message) - { - _latch.countDown(); - super.onClose(code,message); - } - - - } - - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - class TestEchoBroadcastWebSocket extends TestWebSocket - { - @Override - public void onOpen(Connection connection) - { - super.onOpen(connection); - _broadcast.add(this); - } - - @Override - public void onClose(int code,String message) - { - super.onClose(code,message); - _broadcast.remove(this); - } - - @Override - public void onMessage(byte[] data, int offset, int length) - { - super.onMessage(data,offset,length); - for (TestWebSocket ws : _broadcast) - { - try - { - ws.getConnection().sendMessage(data,offset,length); - } - catch (IOException e) - { - _broadcast.remove(ws); - e.printStackTrace(); - } - } - } - - @Override - public void onMessage(final String data) - { - super.onMessage(data); - for (TestWebSocket ws : _broadcast) - { - try - { - ws.getConnection().sendMessage(data); - } - catch (IOException e) - { - _broadcast.remove(ws); - e.printStackTrace(); - } - } - } - } - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - class TestEchoAssembleWebSocket extends TestWebSocket - { - - @Override - public void onOpen(Connection connection) - { - super.onOpen(connection); - connection.setMaxTextMessageSize(64*1024); - connection.setMaxBinaryMessageSize(64*1024); - } - - @Override - public void onMessage(byte[] data, int offset, int length) - { - super.onMessage(data,offset,length); - try - { - getConnection().sendMessage(data,offset,length); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - - @Override - public void onMessage(final String data) - { - super.onMessage(data); - try - { - getConnection().sendMessage(data); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - class TestEchoFragmentWebSocket extends TestWebSocket - { - @Override - public void onOpen(Connection connection) - { - super.onOpen(connection); - connection.setMaxTextMessageSize(64*1024); - connection.setMaxBinaryMessageSize(64*1024); - } - - @Override - public void onMessage(byte[] data, int offset, int length) - { - super.onMessage(data,offset,length); - try - { - getConnection().sendFrame((byte)0x0,getConnection().binaryOpcode(),data,offset,length/2); - getConnection().sendFrame((byte)0x8,getConnection().binaryOpcode(),data,offset+length/2,length-length/2); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - - @Override - public void onMessage(final String message) - { - super.onMessage(message); - try - { - byte[] data = message.getBytes(StringUtil.__UTF8); - int offset=0; - int length=data.length; - getConnection().sendFrame((byte)0x0,getConnection().textOpcode(),data,offset,length/2); - getConnection().sendFrame((byte)0x8,getConnection().textOpcode(),data,offset+length/2,length-length/2); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - - private static void usage() - { - System.err.println("java -cp CLASSPATH "+TestServer.class+" [ OPTIONS ]"); - System.err.println(" -p|--port PORT (default 8080)"); - System.err.println(" -v|--verbose "); - System.err.println(" -d|--docroot file (default 'src/test/webapp')"); - System.exit(1); - } - - public static void main(String... args) - { - try - { - int port=8080; - boolean verbose=false; - String docroot="src/test/webapp"; - - for (int i=0;iJETTY-1463 @@ -42,6 +39,7 @@ import static org.hamcrest.Matchers.notNullValue; public class WebSocketCommTest { private Server server; + private SelectChannelConnector connector; private WebSocketCaptureServlet servlet; private URI serverUri; @@ -49,7 +47,9 @@ public class WebSocketCommTest public void startServer() throws Exception { // Configure Server - server = new Server(0); + server = new Server(); + connector = new SelectChannelConnector(); + server.addConnector(connector); ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); @@ -62,13 +62,12 @@ public class WebSocketCommTest // Start Server server.start(); - Connector conn = server.getConnectors()[0]; - String host = conn.getHost(); + String host = connector.getHost(); if (host == null) { host = "localhost"; } - int port = conn.getLocalPort(); + int port = connector.getLocalPort(); serverUri = new URI(String.format("ws://%s:%d/",host,port)); System.out.printf("Server URI: %s%n",serverUri); } @@ -89,12 +88,12 @@ public class WebSocketCommTest @Test public void testSendTextMessages() throws Exception { - WebSocketClientFactory clientFactory = new WebSocketClientFactory(); - clientFactory.start(); + // WebSocketClientFactory clientFactory = new WebSocketClientFactory(); + // clientFactory.start(); - WebSocketClient wsc = clientFactory.newWebSocketClient(); + // WebSocketClient wsc = clientFactory.newWebSocketClient(); MessageSender sender = new MessageSender(); - wsc.open(serverUri,sender); + // wsc.open(serverUri,sender); try { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketLoadRFC6455Test.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketLoadRFC6455Test.java index 19ee76f4acd..fd3adcafd2d 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketLoadRFC6455Test.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketLoadRFC6455Test.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.jetty.websocket.server; +import static org.junit.Assert.*; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; @@ -22,37 +24,177 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.management.ManagementFactory; import java.net.Socket; +import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; + import javax.servlet.http.HttpServletRequest; import junit.framework.Assert; -import org.eclipse.jetty.io.bio.SocketEndPoint; -import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.io.AsyncEndPoint; +import org.eclipse.jetty.io.StandardByteBufferPool; +import org.eclipse.jetty.server.SelectChannelConnector; 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.thread.QueuedThreadPool; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketGeneratorRFC6455; -import org.eclipse.jetty.websocket.WebSocket.Connection; -import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; -import org.eclipse.jetty.websocket.masks.FixedMasker; -import org.eclipse.jetty.websocket.servlet.WebSocketHandler; +import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.frames.BaseFrame; +import org.eclipse.jetty.websocket.frames.TextFrame; +import org.eclipse.jetty.websocket.generator.Generator; +import org.eclipse.jetty.websocket.parser.Parser; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.assertTrue; - public class WebSocketLoadRFC6455Test { + private static class EchoWebSocket implements WebSocket.OnTextMessage + { + private volatile Connection outbound; + + @Override + public void onClose(int closeCode, String message) + { + } + + @Override + public void onMessage(String data) + { + try + { + // System.err.println(">> "+data); + outbound.sendMessage(data); + } + catch (IOException x) + { + outbound.disconnect(); + } + } + + @Override + public void onOpen(Connection outbound) + { + this.outbound = outbound; + } + } + private class WebSocketClient implements Runnable + { + private final Socket socket; + private final BufferedWriter output; + private final BufferedReader input; + private final int iterations; + private final CountDownLatch latch; + private/* final */AsyncEndPoint _endp; + private final Generator _generator; + private final Parser _parser; + private final Parser.Listener _handler = new Parser.Listener() + { + /* + * public void close(int code,String message) { } + * + * public void onFrame(byte flags, byte opcode, ByteBuffer buffer) { _response=buffer; } + */ + + @Override + public void onFrame(BaseFrame frame) + { + // TODO Auto-generated method stub + } + + @Override + public void onWebSocketException(WebSocketException e) + { + // TODO Auto-generated method stub + + } + }; + private volatile ByteBuffer _response; + + public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException + { + this.latch = latch; + socket = new Socket(host, port); + socket.setSoTimeout(readTimeout); + output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1")); + input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1")); + this.iterations = iterations; + + WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); + + // _endp=new SocketEndPoint(socket); + StandardByteBufferPool bufferPool = new StandardByteBufferPool(); + _generator = new Generator(bufferPool,policy); + _parser = new Parser(policy); + + } + + public void close() throws IOException + { + socket.close(); + } + + private void open() throws IOException + { + 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: 7\r\n"+ + "\r\n"); + output.flush(); + + String responseLine = input.readLine(); + assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols")); + // Read until we find an empty line, which signals the end of the http response + String line; + while ((line = input.readLine()) != null) + { + if (line.length() == 0) + { + break; + } + } + } + + + @Override + public void run() + { + try + { + String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; + for (int i = 0; i < iterations; ++i) + { + TextFrame txt = new TextFrame(); + txt.setData(message); + ByteBuffer buf = _generator.generate(txt); + + // TODO: Send it + // TODO: Receive response + + Assert.assertEquals(message,_response.toString()); + latch.countDown(); + } + } + catch (Throwable x) + { + throw new RuntimeException(x); + } + } + } + private static Server _server; - private static Connector _connector; + + private static SelectChannelConnector _connector; @BeforeClass public static void startServer() throws Exception @@ -68,6 +210,7 @@ public class WebSocketLoadRFC6455Test WebSocketHandler wsHandler = new WebSocketHandler() { + @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return new EchoWebSocket(); @@ -105,16 +248,20 @@ public class WebSocketLoadRFC6455Test //long start = System.nanoTime(); for (WebSocketClient client : clients) + { threadPool.execute(client); + } int parallelism = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); long maxTimePerIteration = 5; - assertTrue(latch.await(iterations * (count / parallelism + 1) * maxTimePerIteration, TimeUnit.MILLISECONDS)); + assertTrue(latch.await(iterations * ((count / parallelism) + 1) * maxTimePerIteration, TimeUnit.MILLISECONDS)); //long end = System.nanoTime(); // System.err.println("Elapsed: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms"); for (WebSocketClient client : clients) + { client.close(); + } } finally { @@ -122,125 +269,4 @@ public class WebSocketLoadRFC6455Test assertTrue(threadPool.awaitTermination(2, TimeUnit.SECONDS)); } } - - private static class EchoWebSocket implements WebSocket.OnTextMessage - { - private volatile Connection outbound; - - public void onOpen(Connection outbound) - { - this.outbound = outbound; - } - - public void onMessage(String data) - { - try - { - // System.err.println(">> "+data); - outbound.sendMessage(data); - } - catch (IOException x) - { - outbound.disconnect(); - } - } - - public void onClose(int closeCode, String message) - { - } - } - - private class WebSocketClient implements Runnable - { - private final Socket socket; - private final BufferedWriter output; - private final BufferedReader input; - private final int iterations; - private final CountDownLatch latch; - private final SocketEndPoint _endp; - private final WebSocketGeneratorRFC6455 _generator; - private final WebSocketParserRFC6455 _parser; - private final WebSocketParser.FrameHandler _handler = new WebSocketParser.FrameHandler() - { - public void onFrame(byte flags, byte opcode, ByteBuffer buffer) - { - _response=buffer; - } - - public void close(int code,String message) - { - } - }; - private volatile ByteBuffer _response; - - public WebSocketClient(String host, int port, int readTimeout, CountDownLatch latch, int iterations) throws IOException - { - this.latch = latch; - socket = new Socket(host, port); - socket.setSoTimeout(readTimeout); - output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1")); - input = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ISO-8859-1")); - this.iterations = iterations; - - _endp=new SocketEndPoint(socket); - _generator = new WebSocketGeneratorRFC6455(new WebSocketBuffers(32*1024),_endp,new FixedMasker()); - _parser = new WebSocketParserRFC6455(new WebSocketBuffers(32*1024),_endp,_handler,false); - - } - - private void open() throws IOException - { - 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: 7\r\n"+ - "\r\n"); - output.flush(); - - String responseLine = input.readLine(); - assertTrue(responseLine.startsWith("HTTP/1.1 101 Switching Protocols")); - // Read until we find an empty line, which signals the end of the http response - String line; - while ((line = input.readLine()) != null) - if (line.length() == 0) - break; - } - - public void run() - { - try - { - String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; - for (int i = 0; i < iterations; ++i) - { - byte[] data = message.getBytes(StringUtil.__UTF8); - _generator.addFrame((byte)0x8,WebSocketConnectionRFC6455.OP_TEXT,data,0,data.length); - _generator.flush(); - - //System.err.println("-> "+message); - - _response=null; - while(_response==null) - _parser.parseNext(); - //System.err.println("<- "+_response); - Assert.assertEquals(message,_response.toString()); - latch.countDown(); - } - } - catch (IOException x) - { - throw new RuntimeException(x); - } - } - - - public void close() throws IOException - { - socket.close(); - } - } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java index 2559e4ed738..15f78bea5da 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java @@ -28,31 +28,19 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; import java.util.zip.Deflater; import java.util.zip.Inflater; import javax.servlet.http.HttpServletRequest; - -import org.eclipse.jetty.io.ByteArrayEndPoint; -import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.SelectChannelConnector; 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.TypeUtil; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocketGeneratorRFC6455; -import org.eclipse.jetty.websocket.WebSocket.Connection; -import org.eclipse.jetty.websocket.WebSocket.FrameConnection; -import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage; -import org.eclipse.jetty.websocket.WebSocket.OnFrame; -import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; -import org.eclipse.jetty.websocket.masks.Masker; -import org.eclipse.jetty.websocket.masks.RandomMasker; -import org.eclipse.jetty.websocket.servlet.WebSocketHandler; +import org.eclipse.jetty.websocket.api.OpCode; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -60,10 +48,140 @@ import org.junit.Test; public class WebSocketMessageRFC6455Test { + private static class TestWebSocket implements WebSocket.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage + { + protected boolean _latch; + boolean _onConnect = false; + boolean _echo = true; + boolean _aggregate = false; + private final CountDownLatch connected = new CountDownLatch(1); + private final CountDownLatch disconnected = new CountDownLatch(1); + private volatile FrameConnection connection; + + 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 FrameConnection getConnection() + { + return connection; + } + + @Override + public void onClose(int code, String message) + { + disconnected.countDown(); + } + + @Override + public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) + { + if (_echo) + { + OpCode op = OpCode.from(opcode); + switch (op) + { + case CLOSE: + case PING: + case PONG: + break; + + default: + try + { + connection.sendFrame(flags,opcode,data,offset,length); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + return false; + } + + @Override + public void onHandshake(FrameConnection connection) + { + this.connection = connection; + } + + @Override + public void onMessage(byte[] data, int offset, int length) + { + if (_aggregate) + { + try + { + connection.sendMessage(data,offset,length); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + @Override + public void onMessage(String data) + { + __textCount.incrementAndGet(); + if (_latch) + { + try + { + __latch.await(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + if (_aggregate) + { + try + { + connection.sendMessage(data); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + @Override + public void onOpen(Connection connection) + { + if (_onConnect) + { + try + { + connection.sendMessage("sent on connect"); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + connected.countDown(); + } + + } + + private static final int WSVERSION = org.eclipse.jetty.websocket.api.WebSocket.VERSION; private static Server __server; - private static Connector __connector; + private static SelectChannelConnector __connector; private static TestWebSocket __serverWebSocket; private static CountDownLatch __latch; + private static AtomicInteger __textCount = new AtomicInteger(0); @BeforeClass @@ -74,6 +192,7 @@ public class WebSocketMessageRFC6455Test __server.addConnector(__connector); WebSocketHandler wsHandler = new WebSocketHandler() { + @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { __textCount.set(0); @@ -83,17 +202,20 @@ public class WebSocketMessageRFC6455Test __serverWebSocket._aggregate=("aggregate".equals(protocol)); __serverWebSocket._latch=("latch".equals(protocol)); if (__serverWebSocket._latch) + { __latch=new CountDownLatch(1); + } return __serverWebSocket; } }; - wsHandler.getWebSocketFactory().setBufferSize(8192); - wsHandler.getWebSocketFactory().setMaxIdleTime(1000); + wsHandler.getWebSocketFactory().getPolicy().setBufferSize(8192); + wsHandler.getWebSocketFactory().getPolicy().setMaxIdleTime(1000); wsHandler.setHandler(new DefaultHandler()); __server.setHandler(wsHandler); __server.start(); } + @AfterClass public static void stopServer() throws Exception { @@ -101,33 +223,82 @@ public class WebSocketMessageRFC6455Test __server.join(); } + 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+"'",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; + } + } + } @Test - public void testHash() - { - assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionRFC6455.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); - } - - @Test - public void testServerSendBigStringMessage() throws Exception + public void testBinaryAggregate() 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: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); + "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: aggregate\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\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); @@ -137,299 +308,39 @@ public class WebSocketMessageRFC6455Test assertTrue(__serverWebSocket.awaitConnected(1000)); assertNotNull(__serverWebSocket.connection); + __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024); - // 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.connection.sendMessage(data); - - assertEquals(WebSocketConnectionRFC6455.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: "+WebSocketConnectionRFC6455.VERSION+"\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.connection); - - assertEquals(0x81,input.read()); - assertEquals(0x0f,input.read()); - lookFor("sent on connect",input); - } - - @Test - public void testIdentityExtension() 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: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "Sec-WebSocket-Extensions: identity;param=0\r\n"+ - "Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \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("Sec-WebSocket-Extensions: ",input); - lookFor("identity;param=0",input); - skipTo("Sec-WebSocket-Extensions: ",input); - lookFor("identity;param=1",input); - skipTo("Sec-WebSocket-Extensions: ",input); - lookFor("identity;",input); - skipTo("\r\n\r\n",input); - - assertTrue(__serverWebSocket.awaitConnected(1000)); - assertNotNull(__serverWebSocket.connection); - - assertEquals(0x81,input.read()); - assertEquals(0x0f,input.read()); - lookFor("sent on connect",input); - } - - - @Test - public void testFragmentExtension() 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: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\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("Sec-WebSocket-Extensions: ",input); - lookFor("fragment;",input); - skipTo("\r\n\r\n",input); - - assertTrue(__serverWebSocket.awaitConnected(1000)); - assertNotNull(__serverWebSocket.connection); - - assertEquals(0x01,input.read()); - assertEquals(0x04,input.read()); - lookFor("sent",input); - assertEquals(0x00,input.read()); - assertEquals(0x04,input.read()); - lookFor(" on ",input); - assertEquals(0x00,input.read()); - assertEquals(0x04,input.read()); - lookFor("conn",input); - assertEquals(0x00,input.read()); - assertEquals(0x01,input.read()); - lookFor("e",input); - assertEquals(0x00,input.read()); - assertEquals(0x01,input.read()); - lookFor("c",input); - assertEquals(0x00,input.read()); - assertEquals(0x00,input.read()); - assertEquals(0x80,input.read()); - assertEquals(0x01,input.read()); - lookFor("t",input); - } - - @Test - public void testDeflateFrameExtension() 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: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+ - "Sec-WebSocket-Extensions: fragment;minFragments=2\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("Sec-WebSocket-Extensions: ",input); - lookFor("x-deflate-frame;minLength=64",input); - skipTo("Sec-WebSocket-Extensions: ",input); - lookFor("fragment;",input); - skipTo("\r\n\r\n",input); - - assertTrue(__serverWebSocket.awaitConnected(1000)); - assertNotNull(__serverWebSocket.connection); - - - // Server sends a big message - String text = "0123456789ABCDEF "; - text=text+text+text+text; - text=text+text+text+text; - text=text+text+text+text+'X'; - byte[] data=text.getBytes("utf-8"); - Deflater deflater = new Deflater(); - deflater.setInput(data); - deflater.finish(); - byte[] buf=new byte[data.length]; - - buf[0]=(byte)((byte)0x7e); - buf[1]=(byte)(data.length>>8); - buf[2]=(byte)(data.length&0xff); - - int l=deflater.deflate(buf,3,buf.length-3); - - assertTrue(deflater.finished()); - - output.write(0xC1); - output.write((byte)(0x80|(0xff&(l+3)))); - output.write(0x00); - output.write(0x00); - output.write(0x00); - output.write(0x00); - output.write(buf,0,l+3); - output.flush(); - - assertEquals(0x40+WebSocketConnectionRFC6455.OP_TEXT,input.read()); - assertEquals(0x20+3,input.read()); - assertEquals(0x7e,input.read()); - assertEquals(0x02,input.read()); - assertEquals(0x20,input.read()); - - byte[] raw = new byte[32]; - assertEquals(32,input.read(raw)); - - Inflater inflater = new Inflater(); - inflater.setInput(raw); - - byte[] result = new byte[544]; - assertEquals(544,inflater.inflate(result)); - assertEquals(TypeUtil.toHexString(data,0,544),TypeUtil.toHexString(result)); - - - assertEquals((byte)0xC0,(byte)input.read()); - assertEquals(0x21+3,input.read()); - assertEquals(0x7e,input.read()); - assertEquals(0x02,input.read()); - assertEquals(0x21,input.read()); - - assertEquals(32,input.read(raw)); - - inflater.reset(); - inflater.setInput(raw); - result = new byte[545]; - assertEquals(545,inflater.inflate(result)); - assertEquals(TypeUtil.toHexString(data,544,545),TypeUtil.toHexString(result)); - - - } - - @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: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); - output.flush(); - output.write(0x84); - output.write(0x8f); + output.write(OpCode.BINARY.getCode()); + output.write(0x8a); output.write(0xff); output.write(0xff); output.write(0xff); output.write(0xff); - byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1); - for (int i=0;i>> "+i); output.flush(); @@ -515,12 +430,14 @@ public class WebSocketMessageRFC6455Test long duration=now-start; start=now; if (max>8)); - output.write((byte)((16*1024)&0xff)); - output.write(0x00); - output.write(0x00); - output.write(0x00); - output.write(0x00); - - for (int i=0;i<(16*1024);i++) - output.write('X'); - output.flush(); - - - assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read()); - assertEquals(33,input.read()); - int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,code); - lookFor("Text message size > 10240 chars",input); - } - - @Test - public void testMaxTextSize() 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: other\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); - output.flush(); - - 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.connection); - - __serverWebSocket.getConnection().setMaxTextMessageSize(15); - - output.write(0x01); - output.write(0x8a); - output.write(0xff); - output.write(0xff); - output.write(0xff); - output.write(0xff); - byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1); - for (int i=0;i 15 chars",input); - } - - - @Test - public void testMaxTextSize2() 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: other\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); - output.flush(); - - socket.setSoTimeout(100000); - 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.connection); - - __serverWebSocket.getConnection().setMaxTextMessageSize(15); - - output.write(0x01); - output.write(0x94); - output.write(0xff); - output.write(0xff); - output.write(0xff); - output.write(0xff); - byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1); - for (int i=0;i 15 chars",input); - } - - @Test - public void testBinaryAggregate() 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: aggregate\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); - output.flush(); - - 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.connection); - __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024); - - output.write(WebSocketConnectionRFC6455.OP_BINARY); - output.write(0x8a); - output.write(0xff); - output.write(0xff); - output.write(0xff); - output.write(0xff); - byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1); - for (int i=0;i 15",input); - } - - - @Test public void testCloseIn() throws Exception { int[][] tests = - { + { {-1,0,-1}, {-1,0,-1}, {1000,2,1000}, @@ -953,10 +557,10 @@ public class WebSocketMessageRFC6455Test {4000,2+4,4000}, {9000,2+23,1002}, {9000,2+23,1002} - }; + }; String[] mesg = - { + { "", "", "", @@ -969,10 +573,10 @@ public class WebSocketMessageRFC6455Test "mesg", "", "mesg" - }; + }; String[] resp = - { + { "", "", "", @@ -985,7 +589,7 @@ public class WebSocketMessageRFC6455Test "mesg", "Invalid close code 9000", "Invalid close code 9000" - }; + }; for (int t=0;t=4) { - code=(0xff&buf[2])*0x100+(0xff&buf[3]); + code=((0xff&buf[2])*0x100)+(0xff&buf[3]); assertEquals(tst,tests[t][2],code); if (len>4) @@ -1053,7 +657,9 @@ public class WebSocketMessageRFC6455Test } } else + { assertEquals(tst,tests[t][2],-1); + } len = input.read(buf); @@ -1061,13 +667,11 @@ public class WebSocketMessageRFC6455Test } } - - @Test public void testCloseOut() throws Exception { int[][] tests = - { + { {-1,0,-1}, {-1,0,-1}, {0,2,1000}, @@ -1080,10 +684,10 @@ public class WebSocketMessageRFC6455Test {1006,0,-1}, {9000,2,9000}, {9000,2+4,9000} - }; + }; String[] mesg = - { + { null, "Not Sent", null, @@ -1096,7 +700,7 @@ public class WebSocketMessageRFC6455Test "mesg", null, "mesg" - }; + }; for (int t=0;t=4) { - int code=(0xff&buf[2])*0x100+(0xff&buf[3]); + int code=((0xff&buf[2])*0x100)+(0xff&buf[3]); assertEquals(tst,tests[t][2],code); if (len>4) @@ -1145,7 +749,9 @@ public class WebSocketMessageRFC6455Test } } else + { assertEquals(tst,tests[t][2],-1); + } try { @@ -1168,102 +774,207 @@ public class WebSocketMessageRFC6455Test } } - @Test - public void testNotUTF8() throws Exception + public void testDeflateFrameExtension() 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\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); + "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: " + WSVERSION + "\r\n" + + "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+ + "Sec-WebSocket-Extensions: fragment;minFragments=2\r\n"+ + "\r\n").getBytes("ISO-8859-1")); output.flush(); - socket.setSoTimeout(100000); - InputStream input = socket.getInputStream(); + // 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("Sec-WebSocket-Extensions: ",input); + lookFor("x-deflate-frame;minLength=64",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("fragment;",input); skipTo("\r\n\r\n",input); assertTrue(__serverWebSocket.awaitConnected(1000)); assertNotNull(__serverWebSocket.connection); - __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); - output.write(0x81); - output.write(0x82); + // Server sends a big message + String text = "0123456789ABCDEF "; + text=text+text+text+text; + text=text+text+text+text; + text=text+text+text+text+'X'; + byte[] data=text.getBytes("utf-8"); + Deflater deflater = new Deflater(); + deflater.setInput(data); + deflater.finish(); + byte[] buf=new byte[data.length]; + + buf[0]=((byte)0x7e); + buf[1]=(byte)(data.length>>8); + buf[2]=(byte)(data.length&0xff); + + int l=deflater.deflate(buf,3,buf.length-3); + + assertTrue(deflater.finished()); + + output.write(0xC1); + output.write((byte)(0x80|(0xff&(l+3)))); output.write(0x00); output.write(0x00); output.write(0x00); output.write(0x00); - output.write(0xc3); - output.write(0x28); + output.write(buf,0,l+3); output.flush(); - assertEquals(0x80|WebSocketConnectionRFC6455.OP_CLOSE,input.read()); - assertEquals(15,input.read()); - int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,code); - lookFor("Invalid UTF-8",input); + assertEquals(0x40 + OpCode.TEXT.getCode(),input.read()); + assertEquals(0x20+3,input.read()); + assertEquals(0x7e,input.read()); + assertEquals(0x02,input.read()); + assertEquals(0x20,input.read()); + + byte[] raw = new byte[32]; + assertEquals(32,input.read(raw)); + + Inflater inflater = new Inflater(); + inflater.setInput(raw); + + byte[] result = new byte[544]; + assertEquals(544,inflater.inflate(result)); + assertEquals(TypeUtil.toHexString(data,0,544),TypeUtil.toHexString(result)); + + + assertEquals((byte)0xC0,(byte)input.read()); + assertEquals(0x21+3,input.read()); + assertEquals(0x7e,input.read()); + assertEquals(0x02,input.read()); + assertEquals(0x21,input.read()); + + assertEquals(32,input.read(raw)); + + inflater.reset(); + inflater.setInput(raw); + result = new byte[545]; + assertEquals(545,inflater.inflate(result)); + assertEquals(TypeUtil.toHexString(data,544,545),TypeUtil.toHexString(result)); + + } @Test - public void testMaxBinarySize2() throws Exception + public void testFragmentExtension() 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: other\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); + "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: " + WSVERSION + "\r\n" + + "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\r\n"+ + "\r\n").getBytes("ISO-8859-1")); output.flush(); - socket.setSoTimeout(100000); - InputStream input = socket.getInputStream(); + // 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("Sec-WebSocket-Extensions: ",input); + lookFor("fragment;",input); skipTo("\r\n\r\n",input); assertTrue(__serverWebSocket.awaitConnected(1000)); assertNotNull(__serverWebSocket.connection); - __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); + assertEquals(0x01,input.read()); + assertEquals(0x04,input.read()); + lookFor("sent",input); + assertEquals(0x00,input.read()); + assertEquals(0x04,input.read()); + lookFor(" on ",input); + assertEquals(0x00,input.read()); + assertEquals(0x04,input.read()); + lookFor("conn",input); + assertEquals(0x00,input.read()); + assertEquals(0x01,input.read()); + lookFor("e",input); + assertEquals(0x00,input.read()); + assertEquals(0x01,input.read()); + lookFor("c",input); + assertEquals(0x00,input.read()); + assertEquals(0x00,input.read()); + assertEquals(0x80,input.read()); + assertEquals(0x01,input.read()); + lookFor("t",input); + } - output.write(0x02); - output.write(0x94); - output.write(0xff); - output.write(0xff); - output.write(0xff); - output.write(0xff); - byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1); - for (int i=0;i 15",input); + // 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("Sec-WebSocket-Extensions: ",input); + lookFor("identity;param=0",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("identity;param=1",input); + skipTo("Sec-WebSocket-Extensions: ",input); + lookFor("identity;",input); + skipTo("\r\n\r\n",input); + + assertTrue(__serverWebSocket.awaitConnected(1000)); + assertNotNull(__serverWebSocket.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); } + @Test public void testIdle() throws Exception { @@ -1271,14 +982,14 @@ public class WebSocketMessageRFC6455Test 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: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); + "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: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); output.flush(); // Make sure the read times out if there are problems with the implementation @@ -1326,6 +1037,513 @@ public class WebSocketMessageRFC6455Test } } + @Test + public void testMaxBinarySize() 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: other\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + 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.connection); + + __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); + + output.write(0x02); + output.write(0x8a); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1); + for (byte b : bytes) + { + output.write(b^0xff); + } + output.flush(); + + output.write(0x80); + output.write(0x8a); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + for (byte b : bytes) + { + output.write(b^0xff); + } + output.flush(); + + + assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); + assertEquals(19,input.read()); + int code=((0xff&input.read())*0x100)+(0xff&input.read()); + assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + lookFor("Message size > 15",input); + } + + @Test + public void testMaxBinarySize2() 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: other\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + 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.connection); + + __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); + + output.write(0x02); + output.write(0x94); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1); + for (byte b : bytes) + { + output.write(b^0xff); + } + output.flush(); + + assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); + assertEquals(19,input.read()); + int code=((0xff&input.read())*0x100)+(0xff&input.read()); + assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + lookFor("Message size > 15",input); + } + + + + @Test + public void testMaxTextSize() 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: other\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + 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.connection); + + __serverWebSocket.getConnection().setMaxTextMessageSize(15); + + output.write(0x01); + output.write(0x8a); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="0123456789".getBytes(StringUtil.__ISO_8859_1); + for (byte b : bytes) + { + output.write(b^0xff); + } + output.flush(); + + output.write(0x80); + output.write(0x8a); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + for (byte b : bytes) + { + output.write(b^0xff); + } + output.flush(); + + assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); + assertEquals(30,input.read()); + int code=((0xff&input.read())*0x100)+(0xff&input.read()); + assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + lookFor("Text message size > 15 chars",input); + } + + + + @Test + public void testMaxTextSize2() 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: other\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + 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.connection); + + __serverWebSocket.getConnection().setMaxTextMessageSize(15); + + output.write(0x01); + output.write(0x94); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="01234567890123456789".getBytes(StringUtil.__ISO_8859_1); + for (byte b : bytes) + { + output.write(b^0xff); + } + output.flush(); + + + + assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); + assertEquals(30,input.read()); + int code=((0xff&input.read())*0x100)+(0xff&input.read()); + assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + lookFor("Text message size > 15 chars",input); + } + + + @Test + public void testMaxTextSizeFalseFrag() 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: other\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + 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.connection); + + __serverWebSocket.getConnection().setMaxTextMessageSize(10*1024); + __serverWebSocket.getConnection().setAllowFrameFragmentation(true); + + output.write(0x81); + output.write(0x80|0x7E); + output.write((byte)((16*1024)>>8)); + output.write((byte)((16*1024)&0xff)); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0x00); + + for (int i=0;i<(16*1024);i++) + { + output.write('X'); + } + output.flush(); + + + assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); + assertEquals(33,input.read()); + int code=((0xff&input.read())*0x100)+(0xff&input.read()); + assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + lookFor("Text message size > 10240 chars",input); + } + + @Test + public void testNotUTF8() 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\r\n" + + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + + socket.setSoTimeout(100000); + 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.connection); + + __serverWebSocket.getConnection().setMaxBinaryMessageSize(15); + + output.write(0x81); + output.write(0x82); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0x00); + output.write(0xc3); + output.write(0x28); + output.flush(); + + assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); + assertEquals(15,input.read()); + int code=((0xff&input.read())*0x100)+(0xff&input.read()); + assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_BAD_PAYLOAD,code); + lookFor("Invalid UTF-8",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: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + output.write(0x84); + output.write(0x8f); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(0xff); + byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1); + for (byte b : bytes) + { + output.write(b^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.connection); + + 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: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); + output.flush(); + output.write(0x89); + output.write(0x80); + output.write(0xff); + output.write(0xff); + output.write(0xff); + output.write(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.connection); + + socket.setSoTimeout(1000); + assertEquals(0x8A,input.read()); + assertEquals(0x00,input.read()); + } + + @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: "+org.eclipse.jetty.websocket.api.WebSocket.VERSION+"\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.connection); + + // 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.connection.sendMessage(data); + + assertEquals(OpCode.TEXT.getCode(),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: " + WSVERSION + "\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.connection); + + assertEquals(0x81,input.read()); + assertEquals(0x0f,input.read()); + lookFor("sent on connect",input); + } + @Test public void testTCPClose() throws Exception { @@ -1339,8 +1557,8 @@ public class WebSocketMessageRFC6455Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); output.flush(); // Make sure the read times out if there are problems with the implementation @@ -1375,6 +1593,7 @@ public class WebSocketMessageRFC6455Test } } + @Test public void testTCPHalfClose() throws Exception { @@ -1388,8 +1607,8 @@ public class WebSocketMessageRFC6455Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+ - "\r\n").getBytes("ISO-8859-1")); + "Sec-WebSocket-Version: " + WSVERSION + "\r\n" + + "\r\n").getBytes("ISO-8859-1")); output.flush(); // Make sure the read times out if there are problems with the implementation @@ -1422,7 +1641,9 @@ public class WebSocketMessageRFC6455Test try { for (int i=0;i<1000;i++) + { output.write(0); + } Assert.fail(); } catch(SocketException e) @@ -1430,241 +1651,4 @@ public class WebSocketMessageRFC6455Test // expected } } - - - - @Test - public void testParserAndGenerator() throws Exception - { - String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; - final AtomicReference received = new AtomicReference(); - ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - - WebSocketGeneratorRFC6455 gen = new WebSocketGeneratorRFC6455(new WebSocketBuffers(8096),endp,null); - - byte[] data = message.getBytes(StringUtil.__UTF8); - gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length); - - endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); - - WebSocketParserRFC6455 parser = new WebSocketParserRFC6455(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() - { - public void onFrame(byte flags, byte opcode, ByteBuffer buffer) - { - received.set(buffer.toString()); - } - - public void close(int code,String message) - { - } - - },false); - - parser.parseNext(); - - assertEquals(message,received.get()); - } - - @Test - public void testParserAndGeneratorMasked() throws Exception - { - String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; - final AtomicReference received = new AtomicReference(); - ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - - Masker maskGen = new RandomMasker(); - - WebSocketGeneratorRFC6455 gen = new WebSocketGeneratorRFC6455(new WebSocketBuffers(8096),endp,maskGen); - byte[] data = message.getBytes(StringUtil.__UTF8); - gen.addFrame((byte)0x8,(byte)0x1,data,0,data.length); - - endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); - - WebSocketParserRFC6455 parser = new WebSocketParserRFC6455(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() - { - public void onFrame(byte flags, byte opcode, ByteBuffer buffer) - { - received.set(buffer.toString()); - } - - public void close(int code,String message) - { - } - },true); - - parser.parseNext(); - - assertEquals(message,received.get()); - } - - - 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.OnFrame, WebSocket.OnBinaryMessage, WebSocket.OnTextMessage - { - protected boolean _latch; - boolean _onConnect=false; - boolean _echo=true; - boolean _aggregate=false; - private final CountDownLatch connected = new CountDownLatch(1); - private final CountDownLatch disconnected = new CountDownLatch(1); - private volatile FrameConnection connection; - - public FrameConnection getConnection() - { - return connection; - } - - public void onHandshake(FrameConnection connection) - { - this.connection = connection; - } - - public void onOpen(Connection connection) - { - if (_onConnect) - { - try - { - connection.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 onClose(int code,String message) - { - disconnected.countDown(); - } - - public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length) - { - if (_echo) - { - switch(opcode) - { - case WebSocketConnectionRFC6455.OP_CLOSE: - case WebSocketConnectionRFC6455.OP_PING: - case WebSocketConnectionRFC6455.OP_PONG: - break; - - default: - try - { - connection.sendFrame(flags,opcode,data,offset,length); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - return false; - } - - public void onMessage(byte[] data, int offset, int length) - { - if (_aggregate) - { - try - { - connection.sendMessage(data,offset,length); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - - public void onMessage(String data) - { - __textCount.incrementAndGet(); - if (_latch) - { - try - { - __latch.await(); - } - catch(Exception e) - { - e.printStackTrace(); - } - } - - if (_aggregate) - { - try - { - connection.sendMessage(data); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - } - - } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java index ff42d5ef1cd..fea0516cba3 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketOverSSLTest.java @@ -16,10 +16,10 @@ package org.eclipse.jetty.websocket.server; import java.io.IOException; -import java.net.URI; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; + import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.Server; @@ -28,12 +28,6 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocket.Connection; -import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.client.WebSocketClientFactory; -import org.eclipse.jetty.websocket.masks.ZeroMasker; -import org.eclipse.jetty.websocket.servlet.WebSocketHandler; import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -43,9 +37,51 @@ public class WebSocketOverSSLTest private Server _server; private int _port; private QueuedThreadPool _threadPool; - private WebSocketClientFactory _wsFactory; +// private WebSocketClientFactory _wsFactory; private WebSocket.Connection _connection; + @After + public void destroy() throws Exception + { + if (_connection != null) + { + _connection.close(); + } + +// if (_wsFactory != null) +// _wsFactory.stop(); + + if (_threadPool != null) + { + _threadPool.stop(); + } + + if (_server != null) + { + _server.stop(); + _server.join(); + } + } + + private void startClient(final WebSocket webSocket) throws Exception + { + Assert.assertTrue(_server.isStarted()); + + _threadPool = new QueuedThreadPool(); + _threadPool.setName("wsc-" + _threadPool.getName()); + _threadPool.start(); + +// _wsFactory = new WebSocketClientFactory(_threadPool, new ZeroMasker()); +// SslContextFactory cf = _wsFactory.getSslContextFactory(); +// cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath()); +// cf.setKeyStorePassword("storepwd"); +// cf.setKeyManagerPassword("keypwd"); +// _wsFactory.start(); + +// WebSocketClient client = new WebSocketClient(_wsFactory); +// _connection = client.open(new URI("wss://localhost:" + _port), webSocket).get(5, TimeUnit.SECONDS); + } + private void startServer(final WebSocket webSocket) throws Exception { _server = new Server(); @@ -57,6 +93,7 @@ public class WebSocketOverSSLTest cf.setKeyManagerPassword("keypwd"); _server.setHandler(new WebSocketHandler() { + @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return webSocket; @@ -66,42 +103,71 @@ public class WebSocketOverSSLTest _port = connector.getLocalPort(); } - private void startClient(final WebSocket webSocket) throws Exception + @Test + public void testManyMessages() throws Exception { - Assert.assertTrue(_server.isStarted()); - - _threadPool = new QueuedThreadPool(); - _threadPool.setName("wsc-" + _threadPool.getName()); - _threadPool.start(); - - _wsFactory = new WebSocketClientFactory(_threadPool, new ZeroMasker()); - SslContextFactory cf = _wsFactory.getSslContextFactory(); - cf.setKeyStorePath(MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath()); - cf.setKeyStorePassword("storepwd"); - cf.setKeyManagerPassword("keypwd"); - _wsFactory.start(); - - WebSocketClient client = new WebSocketClient(_wsFactory); - _connection = client.open(new URI("wss://localhost:" + _port), webSocket).get(5, TimeUnit.SECONDS); - } - - @After - public void destroy() throws Exception - { - if (_connection != null) - _connection.close(); - - if (_wsFactory != null) - _wsFactory.stop(); - - if (_threadPool != null) - _threadPool.stop(); - - if (_server != null) + startServer(new WebSocket.OnTextMessage() { - _server.stop(); - _server.join(); + private Connection connection; + + @Override + public void onClose(int closeCode, String message) + { + } + + @Override + public void onMessage(String data) + { + try + { + connection.sendMessage(data); + } + catch (IOException x) + { + x.printStackTrace(); + } + } + + @Override + public void onOpen(Connection connection) + { + this.connection = connection; + } + }); + int count = 1000; + final CountDownLatch clientLatch = new CountDownLatch(count); + startClient(new WebSocket.OnTextMessage() + { + @Override + public void onClose(int closeCode, String message) + { + } + + @Override + public void onMessage(String data) + { + clientLatch.countDown(); + } + + @Override + public void onOpen(Connection connection) + { + } + }); + + char[] chars = new char[256]; + Arrays.fill(chars, 'x'); + String message = new String(chars); + for (int i = 0; i < count; ++i) + { + _connection.sendMessage(message); } + + Assert.assertTrue(clientLatch.await(20, TimeUnit.SECONDS)); + + // While messages may have all arrived, the SSL close alert + // may be in the way so give some time for it to be processed. + TimeUnit.SECONDS.sleep(1); } @Test @@ -113,11 +179,12 @@ public class WebSocketOverSSLTest { private Connection connection; - public void onOpen(Connection connection) + @Override + public void onClose(int closeCode, String message) { - this.connection = connection; } + @Override public void onMessage(String data) { try @@ -132,24 +199,29 @@ public class WebSocketOverSSLTest } } - public void onClose(int closeCode, String message) + @Override + public void onOpen(Connection connection) { + this.connection = connection; } }); final CountDownLatch clientLatch = new CountDownLatch(1); startClient(new WebSocket.OnTextMessage() { - public void onOpen(Connection connection) + @Override + public void onClose(int closeCode, String message) { } + @Override public void onMessage(String data) { Assert.assertEquals(message, data); clientLatch.countDown(); } - public void onClose(int closeCode, String message) + @Override + public void onOpen(Connection connection) { } }); @@ -158,63 +230,4 @@ public class WebSocketOverSSLTest Assert.assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); } - - @Test - public void testManyMessages() throws Exception - { - startServer(new WebSocket.OnTextMessage() - { - private Connection connection; - - public void onOpen(Connection connection) - { - this.connection = connection; - } - - public void onMessage(String data) - { - try - { - connection.sendMessage(data); - } - catch (IOException x) - { - x.printStackTrace(); - } - } - - public void onClose(int closeCode, String message) - { - } - }); - int count = 1000; - final CountDownLatch clientLatch = new CountDownLatch(count); - startClient(new WebSocket.OnTextMessage() - { - public void onOpen(Connection connection) - { - } - - public void onMessage(String data) - { - clientLatch.countDown(); - } - - public void onClose(int closeCode, String message) - { - } - }); - - char[] chars = new char[256]; - Arrays.fill(chars, 'x'); - String message = new String(chars); - for (int i = 0; i < count; ++i) - _connection.sendMessage(message); - - Assert.assertTrue(clientLatch.await(20, TimeUnit.SECONDS)); - - // While messages may have all arrived, the SSL close alert - // may be in the way so give some time for it to be processed. - TimeUnit.SECONDS.sleep(1); - } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketRedeployTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketRedeployTest.java index 41eb1dcbef6..b373c8c69cc 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketRedeployTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketRedeployTest.java @@ -16,13 +16,17 @@ package org.eclipse.jetty.websocket.server; -import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.server.SelectChannelConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.WebSocket.Connection; -import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -32,15 +36,16 @@ public class WebSocketRedeployTest private Server server; private ServletContextHandler context; private String uri; - private WebSocketClientFactory wsFactory; + + // private WebSocketClientFactory wsFactory; @After public void destroy() throws Exception { - if (wsFactory != null) - { - wsFactory.stop(); - } + // if (wsFactory != null) + // { + // wsFactory.stop(); + // } if (server != null) { server.stop(); @@ -52,7 +57,7 @@ public class WebSocketRedeployTest { server = new Server(); SelectChannelConnector connector = new SelectChannelConnector(); -// connector.setPort(8080); + // connector.setPort(8080); server.addConnector(connector); HandlerCollection handlers = new HandlerCollection(); @@ -63,6 +68,7 @@ public class WebSocketRedeployTest WebSocketServlet servlet = new WebSocketServlet() { + @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return webSocket; @@ -75,8 +81,8 @@ public class WebSocketRedeployTest uri = "ws://localhost:" + connector.getLocalPort() + contextPath + servletPath; - wsFactory = new WebSocketClientFactory(); - wsFactory.start(); + // wsFactory = new WebSocketClientFactory(); + // wsFactory.start(); } @Test @@ -104,30 +110,30 @@ public class WebSocketRedeployTest } }); - WebSocketClient client = wsFactory.newWebSocketClient(); - client.open(new URI(uri), new WebSocket.OnTextMessage() - { - @Override - public void onClose(int closeCode, String message) - { - closeLatch.countDown(); - } - - @Override - public void onMessage(String data) - { - } - - @Override - public void onOpen(Connection connection) - { - openLatch.countDown(); - } - }, 5, TimeUnit.SECONDS); + // WebSocketClient client = wsFactory.newWebSocketClient(); + // client.open(new URI(uri), new WebSocket.OnTextMessage() + // { + // @Override + // public void onClose(int closeCode, String message) + // { + // closeLatch.countDown(); + // } + // + // @Override + // public void onMessage(String data) + // { + // } + // + // @Override + // public void onOpen(Connection connection) + // { + // openLatch.countDown(); + // } + // }, 5, TimeUnit.SECONDS); Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS)); - wsFactory.stop(); + // wsFactory.stop(); Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); } @@ -157,26 +163,26 @@ public class WebSocketRedeployTest } }); - WebSocketClient client = wsFactory.newWebSocketClient(); - client.open(new URI(uri), new WebSocket.OnTextMessage() - { - @Override - public void onClose(int closeCode, String message) - { - closeLatch.countDown(); - } - - @Override - public void onMessage(String data) - { - } - - @Override - public void onOpen(Connection connection) - { - openLatch.countDown(); - } - }, 5, TimeUnit.SECONDS); + // WebSocketClient client = wsFactory.newWebSocketClient(); + // client.open(new URI(uri), new WebSocket.OnTextMessage() + // { + // @Override + // public void onClose(int closeCode, String message) + // { + // closeLatch.countDown(); + // } + // + // @Override + // public void onMessage(String data) + // { + // } + // + // @Override + // public void onOpen(Connection connection) + // { + // openLatch.countDown(); + // } + // }, 5, TimeUnit.SECONDS); Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS)); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java index aa425ee7b4a..b6ba1bd68c0 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java @@ -15,20 +15,14 @@ import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; -import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.SelectChannelConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocketGeneratorRFC6455Test; -import org.eclipse.jetty.websocket.WebSocketParserRFC6455Test; -import org.eclipse.jetty.websocket.WebSocket.Connection; -import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.client.WebSocketClientFactory; -import org.eclipse.jetty.websocket.servlet.helper.MessageSender; -import org.eclipse.jetty.websocket.servlet.helper.WebSocketServlet; +import org.eclipse.jetty.websocket.server.helper.MessageSender; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -43,20 +37,27 @@ import org.junit.Test; */ public class WebSocketServletRFCTest { + @SuppressWarnings("serial") + private static class RFCServlet extends WebSocketServlet + { + @Override + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + { + return new RFCSocket(); + } + } + private static class RFCSocket implements WebSocket, WebSocket.OnTextMessage { private Connection conn; - public void onOpen(Connection connection) - { - this.conn = connection; - } - + @Override public void onClose(int closeCode, String message) { this.conn = null; } + @Override public void onMessage(String data) { // Test the RFC 6455 close code 1011 that should close @@ -77,25 +78,25 @@ public class WebSocketServletRFCTest } } - } - - @SuppressWarnings("serial") - private static class RFCServlet extends WebSocketServlet - { - public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) + @Override + public void onOpen(Connection connection) { - return new RFCSocket(); + this.conn = connection; } + } private static Server server; + private static SelectChannelConnector connector; private static URI serverUri; @BeforeClass public static void startServer() throws Exception { // Configure Server - server = new Server(0); + server = new Server(); + connector = new SelectChannelConnector(); + server.addConnector(connector); ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); @@ -107,13 +108,12 @@ public class WebSocketServletRFCTest // Start Server server.start(); - Connector conn = server.getConnectors()[0]; - String host = conn.getHost(); + String host = connector.getHost(); if (host == null) { host = "localhost"; } - int port = conn.getLocalPort(); + int port = connector.getLocalPort(); serverUri = new URI(String.format("ws://%s:%d/",host,port)); System.out.printf("Server URI: %s%n",serverUri); } @@ -131,6 +131,59 @@ public class WebSocketServletRFCTest } } + private String readResponseHeader(InputStream in) throws IOException + { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader reader = new BufferedReader(isr); + StringBuilder header = new StringBuilder(); + // Read the response header + String line = reader.readLine(); + Assert.assertNotNull(line); + Assert.assertThat(line,startsWith("HTTP/1.1 ")); + header.append(line).append("\r\n"); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + { + break; + } + header.append(line).append("\r\n"); + } + return header.toString(); + } + + /** + * Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal + * server error) being produced by the extended WebSocketServlet. + */ + @Test + public void testResponseOnInternalError() throws Exception + { + // WebSocketClientFactory clientFactory = new WebSocketClientFactory(); + // clientFactory.start(); + + // WebSocketClient wsc = clientFactory.newWebSocketClient(); + MessageSender sender = new MessageSender(); + // wsc.open(serverUri,sender); + + try + { + sender.awaitConnect(); + + sender.sendMessage("CRASH"); + + // Give servlet 500 millisecond to process messages + TimeUnit.MILLISECONDS.sleep(500); + + Assert.assertThat("WebSocket should be closed",sender.isConnected(),is(false)); + Assert.assertThat("WebSocket close clode",sender.getCloseCode(),is(1011)); + } + finally + { + sender.close(); + } + } + /** * Test the requirement of responding with an http 400 when using a Sec-WebSocket-Version that is unsupported. */ @@ -177,57 +230,4 @@ public class WebSocketServletRFCTest socket.close(); } } - - private String readResponseHeader(InputStream in) throws IOException - { - InputStreamReader isr = new InputStreamReader(in); - BufferedReader reader = new BufferedReader(isr); - StringBuilder header = new StringBuilder(); - // Read the response header - String line = reader.readLine(); - Assert.assertNotNull(line); - Assert.assertThat(line,startsWith("HTTP/1.1 ")); - header.append(line).append("\r\n"); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - { - break; - } - header.append(line).append("\r\n"); - } - return header.toString(); - } - - /** - * Test the requirement of responding with server terminated close code 1011 when there is an unhandled (internal - * server error) being produced by the extended WebSocketServlet. - */ - @Test - public void testResponseOnInternalError() throws Exception - { - WebSocketClientFactory clientFactory = new WebSocketClientFactory(); - clientFactory.start(); - - WebSocketClient wsc = clientFactory.newWebSocketClient(); - MessageSender sender = new MessageSender(); - wsc.open(serverUri,sender); - - try - { - sender.awaitConnect(); - - sender.sendMessage("CRASH"); - - // Give servlet 500 millisecond to process messages - TimeUnit.MILLISECONDS.sleep(500); - - Assert.assertThat("WebSocket should be closed",sender.isConnected(),is(false)); - Assert.assertThat("WebSocket close clode",sender.getCloseCode(),is(1011)); - } - finally - { - sender.close(); - } - } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java index a4d88dcbc61..7ea4c5dabc1 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/SafariD00.java @@ -15,6 +15,8 @@ *******************************************************************************/ package org.eclipse.jetty.websocket.server.helper; +import static org.hamcrest.Matchers.*; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -25,13 +27,12 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.URI; +import java.nio.ByteBuffer; -import org.eclipse.jetty.io.ByteArrayBuffer; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.TypeUtil; import org.junit.Assert; -import static org.hamcrest.Matchers.is; - public class SafariD00 { private URI uri; @@ -63,6 +64,11 @@ public class SafariD00 return socket; } + public void disconnect() throws IOException + { + socket.close(); + } + /** * Issue an Http websocket (Draft-0) upgrade request using the Safari particulars. * @@ -126,7 +132,7 @@ public class SafariD00 len += (msg.length() + 2); } - ByteArrayBuffer buf = new ByteArrayBuffer(len); + ByteBuffer buf = ByteBuffer.allocate(len); for (String msg : msgs) { @@ -135,12 +141,7 @@ public class SafariD00 buf.put((byte)0xFF); } - out.write(buf.array()); + BufferUtil.writeTo(buf,out); out.flush(); } - - public void disconnect() throws IOException - { - socket.close(); - } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java index 3a683db200f..5fb05717c75 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/WebSocketCaptureServlet.java @@ -24,9 +24,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.websocket.WebSocket; -import org.eclipse.jetty.websocket.servlet.helper.WebSocketServlet; +import org.eclipse.jetty.websocket.server.WebSocketServlet; -@SuppressWarnings("serial") +@SuppressWarnings("serial") public class WebSocketCaptureServlet extends WebSocketServlet { public List captures = new ArrayList();; @@ -37,6 +37,7 @@ public class WebSocketCaptureServlet extends WebSocketServlet resp.sendError(404); } + @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { CaptureSocket capture = new CaptureSocket();