Bug 391140 - Implement x-webkit-deflate-frame extension as-used by Chrome/Safari

This commit is contained in:
Joakim Erdfelt 2012-10-04 10:07:51 -07:00
parent b18a5b05dc
commit d23215f3f4
24 changed files with 1188 additions and 142 deletions

View File

@ -230,6 +230,9 @@ public class UpgradeConnection extends AbstractConnection
// Connect extensions
if (extensions != null)
{
connection.getParser().configureFromExtensions(extensions);
connection.getGenerator().configureFromExtensions(extensions);
Iterator<Extension> extIter;
// Connect outgoings
extIter = extensions.iterator();
@ -238,23 +241,6 @@ public class UpgradeConnection extends AbstractConnection
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
// Handle RSV reservations
if (ext.useRsv1())
{
connection.getGenerator().setRsv1InUse(true);
connection.getParser().setRsv1InUse(true);
}
if (ext.useRsv2())
{
connection.getGenerator().setRsv2InUse(true);
connection.getParser().setRsv2InUse(true);
}
if (ext.useRsv3())
{
connection.getGenerator().setRsv3InUse(true);
connection.getParser().setRsv3InUse(true);
}
}
// Connect incomings

View File

@ -368,6 +368,8 @@ public class BlockheadServer
// Connect extensions
if (!extensions.isEmpty())
{
generator.configureFromExtensions(extensions);
Iterator<Extension> extIter;
// Connect outgoings
extIter = extensions.iterator();
@ -376,20 +378,6 @@ public class BlockheadServer
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
// Handle RSV reservations
if (ext.useRsv1())
{
generator.setRsv1InUse(true);
}
if (ext.useRsv2())
{
generator.setRsv2InUse(true);
}
if (ext.useRsv3())
{
generator.setRsv3InUse(true);
}
}
// Connect incomings

View File

@ -88,6 +88,58 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
nextIncoming(frame);
}
/**
* Used to indicate that the extension makes use of the RSV1 bit of the base websocket framing.
* <p>
* This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV1.
*
* @return true if extension uses RSV1 for its own purposes.
*/
public boolean isRsv1User()
{
return false;
}
/**
* Used to indicate that the extension makes use of the RSV2 bit of the base websocket framing.
* <p>
* This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV2.
*
* @return true if extension uses RSV2 for its own purposes.
*/
public boolean isRsv2User()
{
return false;
}
/**
* Used to indicate that the extension makes use of the RSV3 bit of the base websocket framing.
* <p>
* This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV3.
*
* @return true if extension uses RSV3 for its own purposes.
*/
public boolean isRsv3User()
{
return false;
}
/**
* Used to indicate that the extension works as a decoder of TEXT Data Frames.
* <p>
* This is used to adjust validation during parsing/generating, as per spec TEXT Data Frames can only contain UTF8 encoded String data.
* <p>
* Example: a compression extension will process a compressed set of text data, the parser/generator should no longer be concerned about the validity of the
* TEXT Data Frames as this is now the responsibility of the extension.
*
* @return true if extension will process TEXT Data Frames, false if extension makes no modifications of TEXT Data Frames. If false, the parser/generator is
* now free to validate the conformance to spec of TEXT Data Frames.
*/
public boolean isTextDataDecoder()
{
return false;
}
/**
* Convenience method for {@link #getNextIncomingFrames()#incoming(WebSocketException)}
*
@ -173,19 +225,4 @@ public abstract class Extension implements OutgoingFrames, IncomingFrames
{
this.policy = policy;
}
public boolean useRsv1()
{
return false;
}
public boolean useRsv2()
{
return false;
}
public boolean useRsv3()
{
return false;
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.ExtensionRegistry;
import org.eclipse.jetty.websocket.core.api.WebSocketException;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.core.extensions.deflate.WebkitDeflateFrameExtension;
import org.eclipse.jetty.websocket.core.extensions.fragment.FragmentExtension;
import org.eclipse.jetty.websocket.core.extensions.identity.IdentityExtension;
import org.eclipse.jetty.websocket.core.extensions.permessage.CompressExtension;
@ -50,6 +51,7 @@ public class WebSocketExtensionRegistry implements ExtensionRegistry
this.registry.put("identity",IdentityExtension.class);
this.registry.put("fragment",FragmentExtension.class);
this.registry.put("x-webkit-deflate-frame",WebkitDeflateFrameExtension.class);
this.registry.put("permessage-compress",CompressExtension.class);
}

View File

@ -0,0 +1,225 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.core.extensions.deflate;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
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.core.api.BadPayloadException;
import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
/**
* Implementation of the <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-05.txt">x-webkit-deflate-frame</a> extension seen out
* in the wild.
*/
public class WebkitDeflateFrameExtension extends Extension
{
private static final Logger LOG = Log.getLogger(WebkitDeflateFrameExtension.class);
private static final int BUFFER_SIZE = 64 * 1024;
private Deflater deflater;
private Inflater inflater;
public ByteBuffer deflate(ByteBuffer data)
{
int length = data.remaining();
// prepare the uncompressed input
deflater.reset();
deflater.setInput(BufferUtil.toArray(data));
deflater.finish();
// prepare the output buffer
int bufsize = Math.max(BUFFER_SIZE,length * 2);
ByteBuffer buf = getBufferPool().acquire(bufsize,false);
BufferUtil.clearToFill(buf);
if (LOG.isDebugEnabled())
{
LOG.debug("Uncompressed length={} - {}",length,buf.position());
}
while (!deflater.finished())
{
byte out[] = new byte[BUFFER_SIZE];
int len = deflater.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
if (LOG.isDebugEnabled())
{
LOG.debug("Deflater: finished={}, needsInput={}, len={} / input.len={}",deflater.finished(),deflater.needsInput(),len,length);
}
buf.put(out,0,len);
}
BufferUtil.flipToFlush(buf,0);
/* Per the spec, it says that BFINAL 1 or 0 are allowed.
* However, Java always uses BFINAL 1, where the browsers
* Chrome and Safari fail to decompress when it encounters
* BFINAL 1.
*
* This hack will always set BFINAL 0
*/
byte b0 = buf.get(0);
if ((b0 & 1) != 0) // if BFINAL 1
{
buf.put(0,(b0 ^= 1)); // flip bit to BFINAL 0
}
return buf;
}
@Override
public void incoming(WebSocketFrame frame)
{
if (frame.isControlFrame() || !frame.isRsv1())
{
// Cannot modify incoming control frames or ones with RSV1 set.
super.incoming(frame);
return;
}
LOG.debug("Decompressing Frame: {}",frame);
ByteBuffer data = frame.getPayload();
try
{
ByteBuffer uncompressed = inflate(data);
frame.setPayload(uncompressed);
nextIncoming(frame);
}
finally
{
// release original buffer (no longer needed)
getBufferPool().release(data);
}
}
public ByteBuffer inflate(ByteBuffer data)
{
if (LOG.isDebugEnabled())
{
LOG.debug("inflate: {}",BufferUtil.toDetailString(data));
LOG.debug("raw data: {}",TypeUtil.toHexString(BufferUtil.toArray(data)));
}
// Set the data that is compressed to the inflater
byte compressed[] = BufferUtil.toArray(data);
inflater.reset();
inflater.setInput(compressed,0,compressed.length);
// Establish place for inflated data
byte buf[] = new byte[BUFFER_SIZE];
try
{
int inflated = inflater.inflate(buf);
if (inflated == 0)
{
throw new DataFormatException("Insufficient compressed data");
}
ByteBuffer ret = ByteBuffer.wrap(buf,0,inflated);
if (LOG.isDebugEnabled())
{
LOG.debug("uncompressed={}",BufferUtil.toDetailString(ret));
}
return ret;
}
catch (DataFormatException e)
{
LOG.warn(e);
throw new BadPayloadException(e);
}
}
/**
* Indicates use of RSV1 flag for indicating deflation is in use.
* <p>
* Also known as the "COMP" framing header bit
*/
@Override
public boolean isRsv1User()
{
return true;
}
/**
* Indicate that this extensions is now responsible for TEXT Data Frame compliance to the WebSocket spec.
*/
@Override
public boolean isTextDataDecoder()
{
return true;
}
@Override
public <C> void output(C context, Callback<C> callback, WebSocketFrame frame) throws IOException
{
if (frame.isControlFrame())
{
// skip, cannot compress control frames.
nextOutput(context,callback,frame);
return;
}
ByteBuffer data = frame.getPayload();
try
{
// deflate data
ByteBuffer buf = deflate(data);
frame.setPayload(buf);
frame.setRsv1(true);
nextOutput(context,callback,frame);
}
finally
{
// free original data buffer
getBufferPool().release(data);
}
}
@Override
public void setConfig(ExtensionConfig config)
{
super.setConfig(config);
boolean nowrap = true;
deflater = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
inflater = new Inflater(nowrap);
}
@Override
public String toString()
{
return String.format("DeflateFrameExtension[]");
}
}

View File

@ -267,7 +267,7 @@ public class CompressExtension extends Extension
* Indicates use of RSV1 flag for indicating deflation is in use.
*/
@Override
public boolean useRsv1()
public boolean isRsv1User()
{
return true;
}

View File

@ -23,11 +23,11 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
/**
* Binary payload validator does nothing, essentially.
* payload validator does no validation.
*/
public class BinaryValidator implements PayloadProcessor
public class NoOpValidator implements PayloadProcessor
{
public static final BinaryValidator INSTANCE = new BinaryValidator();
public static final NoOpValidator INSTANCE = new NoOpValidator();
@Override
public void process(ByteBuffer payload)

View File

@ -19,11 +19,13 @@
package org.eclipse.jetty.websocket.core.protocol;
import java.nio.ByteBuffer;
import java.util.List;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.ProtocolException;
import org.eclipse.jetty.websocket.core.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
@ -164,6 +166,31 @@ public class Generator
}
public void configureFromExtensions(List<? extends Extension> exts)
{
// default
this.rsv1InUse = false;
this.rsv2InUse = false;
this.rsv3InUse = false;
// configure from list of extensions in use
for(Extension ext: exts)
{
if (ext.isRsv1User())
{
this.rsv1InUse = true;
}
if (ext.isRsv2User())
{
this.rsv2InUse = true;
}
if (ext.isRsv3User())
{
this.rsv3InUse = true;
}
}
}
/**
* Generate, into a ByteBuffer, no more than bufferSize of contents from the frame. If the frame exceeds the bufferSize, then multiple calls to
* {@link #generate(int, WebSocketFrame)} are required to obtain each window of ByteBuffer to complete the frame.
@ -368,21 +395,6 @@ public class Generator
return rsv3InUse;
}
public void setRsv1InUse(boolean rsv1InUse)
{
this.rsv1InUse = rsv1InUse;
}
public void setRsv2InUse(boolean rsv2InUse)
{
this.rsv2InUse = rsv2InUse;
}
public void setRsv3InUse(boolean rsv3InUse)
{
this.rsv3InUse = rsv3InUse;
}
@Override
public String toString()
{

View File

@ -19,18 +19,20 @@
package org.eclipse.jetty.websocket.core.protocol;
import java.nio.ByteBuffer;
import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.MessageTooLargeException;
import org.eclipse.jetty.websocket.core.api.ProtocolException;
import org.eclipse.jetty.websocket.core.api.WebSocketException;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.core.io.IncomingFrames;
import org.eclipse.jetty.websocket.core.io.payload.BinaryValidator;
import org.eclipse.jetty.websocket.core.io.payload.CloseReasonValidator;
import org.eclipse.jetty.websocket.core.io.payload.DeMaskProcessor;
import org.eclipse.jetty.websocket.core.io.payload.NoOpValidator;
import org.eclipse.jetty.websocket.core.io.payload.PayloadProcessor;
import org.eclipse.jetty.websocket.core.io.payload.UTF8Validator;
@ -71,6 +73,8 @@ public class Parser
private boolean rsv2InUse = false;
/** Is there an extension using RSV3 */
private boolean rsv3InUse = false;
/** Is there an extension that processes invalid UTF8 text messages (such as compressed content) */
private boolean isTextFrameValidated = true;
private static final Logger LOG = Log.getLogger(Parser.class);
private IncomingFrames incomingFramesHandler;
@ -111,6 +115,36 @@ public class Parser
}
}
public void configureFromExtensions(List<? extends Extension> exts)
{
// default
this.rsv1InUse = false;
this.rsv2InUse = false;
this.rsv3InUse = false;
this.isTextFrameValidated = true;
// configure from list of extensions in use
for(Extension ext: exts)
{
if (ext.isRsv1User())
{
this.rsv1InUse = true;
}
if (ext.isRsv2User())
{
this.rsv2InUse = true;
}
if (ext.isRsv3User())
{
this.rsv3InUse = true;
}
if (ext.isTextDataDecoder())
{
this.isTextFrameValidated = false;
}
}
}
public IncomingFrames getIncomingFramesHandler()
{
return incomingFramesHandler;
@ -262,7 +296,10 @@ public class Parser
throw new ProtocolException("Unknown opcode: " + opc);
}
LOG.debug("OpCode {}, fin={}",OpCode.name(opcode),fin);
if (LOG.isDebugEnabled())
{
LOG.debug("OpCode {}, fin={} rsv={}{}{}",OpCode.name(opcode),fin,(rsv1?'1':'.'),(rsv2?'1':'.'),(rsv3?'1':'.'));
}
/*
* RFC 6455 Section 5.2
@ -290,13 +327,20 @@ public class Parser
switch (opcode)
{
case OpCode.TEXT:
strictnessProcessor = new UTF8Validator();
if (isTextFrameValidated)
{
strictnessProcessor = new UTF8Validator();
}
else
{
strictnessProcessor = NoOpValidator.INSTANCE;
}
break;
case OpCode.CLOSE:
strictnessProcessor = new CloseReasonValidator();
break;
default:
strictnessProcessor = BinaryValidator.INSTANCE;
strictnessProcessor = NoOpValidator.INSTANCE;
break;
}
@ -539,21 +583,6 @@ public class Parser
this.incomingFramesHandler = incoming;
}
public void setRsv1InUse(boolean rsv1InUse)
{
this.rsv1InUse = rsv1InUse;
}
public void setRsv2InUse(boolean rsv2InUse)
{
this.rsv2InUse = rsv2InUse;
}
public void setRsv3InUse(boolean rsv3InUse)
{
this.rsv3InUse = rsv3InUse;
}
@Override
public String toString()
{

View File

@ -23,7 +23,7 @@ import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(
{ DeflateFrameExtensionTest.class, FragmentExtensionTest.class, IdentityExtensionTest.class })
{ CompressMessageExtensionTest.class, FragmentExtensionTest.class, IdentityExtensionTest.class, WebkitDeflateFrameExtensionTest.class })
public class AllTests
{
/* nothing to do here, its all done in the annotations */

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.core.extensions;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -36,19 +38,12 @@ import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
import org.eclipse.jetty.websocket.core.protocol.IncomingFramesCapture;
import org.eclipse.jetty.websocket.core.protocol.OpCode;
import org.eclipse.jetty.websocket.core.protocol.OutgoingFramesCapture;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.eclipse.jetty.websocket.core.protocol.OutgoingFramesCapture.Write;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
public class DeflateFrameExtensionTest
public class CompressMessageExtensionTest
{
/**
* Test a large payload (a payload length over 65535 bytes)
@ -206,7 +201,7 @@ public class DeflateFrameExtensionTest
CompressExtension ext = new CompressExtension();
ext.setBufferPool(new MappedByteBufferPool());
ext.setPolicy(WebSocketPolicy.newServerPolicy());
ExtensionConfig config = ExtensionConfig.parse("x-deflate-frame;minLength=16");
ExtensionConfig config = ExtensionConfig.parse("permessage-compress");
ext.setConfig(config);
ext.setNextIncomingFrames(capture);

View File

@ -0,0 +1,282 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.core.extensions;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.websocket.core.ByteBufferAssert;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.core.extensions.deflate.WebkitDeflateFrameExtension;
import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
import org.eclipse.jetty.websocket.core.protocol.Generator;
import org.eclipse.jetty.websocket.core.protocol.IncomingFramesCapture;
import org.eclipse.jetty.websocket.core.protocol.OpCode;
import org.eclipse.jetty.websocket.core.protocol.OutgoingNetworkBytesCapture;
import org.eclipse.jetty.websocket.core.protocol.Parser;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.junit.Assert;
import org.junit.Test;
public class WebkitDeflateFrameExtensionTest
{
private void assertIncoming(byte[] raw, String... expectedTextDatas)
{
WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
IncomingFramesCapture capture = new IncomingFramesCapture();
WebkitDeflateFrameExtension ext = new WebkitDeflateFrameExtension();
ext.setBufferPool(new MappedByteBufferPool());
ext.setPolicy(policy);
ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
ext.setConfig(config);
ext.setNextIncomingFrames(capture);
Parser parser = new Parser(policy);
parser.configureFromExtensions(Collections.singletonList(ext));
parser.setIncomingFramesHandler(ext);
parser.parse(ByteBuffer.wrap(raw));
int len = expectedTextDatas.length;
capture.assertFrameCount(len);
capture.assertHasFrame(OpCode.TEXT,len);
for (int i = 0; i < len; i++)
{
WebSocketFrame actual = capture.getFrames().get(i);
String prefix = "Frame[" + i + "]";
Assert.assertThat(prefix + ".opcode",actual.getOpCode(),is(OpCode.TEXT));
Assert.assertThat(prefix + ".fin",actual.isFin(),is(true));
Assert.assertThat(prefix + ".rsv1",actual.isRsv1(),is(false)); // RSV1 should be unset at this point
Assert.assertThat(prefix + ".rsv2",actual.isRsv2(),is(false));
Assert.assertThat(prefix + ".rsv3",actual.isRsv3(),is(false));
ByteBuffer expected = BufferUtil.toBuffer(expectedTextDatas[i],StringUtil.__UTF8_CHARSET);
Assert.assertThat(prefix + ".payloadLength",actual.getPayloadLength(),is(expected.remaining()));
ByteBufferAssert.assertEquals(prefix + ".payload",expected,actual.getPayload().slice());
}
}
private void assertOutgoing(String text, String expectedHex) throws IOException
{
WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
WebkitDeflateFrameExtension ext = new WebkitDeflateFrameExtension();
ext.setBufferPool(new MappedByteBufferPool());
ext.setPolicy(policy);
ExtensionConfig config = ExtensionConfig.parse("x-webkit-deflate-frame");
ext.setConfig(config);
ByteBufferPool bufferPool = new MappedByteBufferPool();
boolean validating = true;
Generator generator = new Generator(policy,bufferPool,validating);
generator.configureFromExtensions(Collections.singletonList(ext));
OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator);
ext.setNextOutgoingFrames(capture);
WebSocketFrame frame = WebSocketFrame.text(text);
ext.output(null,new FutureCallback<Void>(),frame);
capture.assertBytes(0,expectedHex);
}
private String deflate(byte data[], int level, boolean nowrap, int strategy, int flush)
{
Deflater compressor = new Deflater(level,nowrap);
compressor.setStrategy(strategy);
// Prime the compressor
compressor.reset();
compressor.setInput(data,0,data.length);
compressor.finish();
byte out[] = new byte[64];
int len = compressor.deflate(out,0,out.length,flush);
compressor.end();
String ret = TypeUtil.toHexString(out,0,len);
System.out.printf("deflate(l=%d,s=%d,f=%d,w=%-5b) => %s%n",level,strategy,flush,nowrap,ret);
return ret;
}
@Test
public void testAllDeflate() throws Exception
{
int strategies[] = new int[] {
Deflater.DEFAULT_STRATEGY,
Deflater.FILTERED,
Deflater.HUFFMAN_ONLY
};
int flushes[] = new int[] {
Deflater.FULL_FLUSH,
Deflater.NO_FLUSH,
Deflater.SYNC_FLUSH
};
byte uncompressed[] = StringUtil.getUtf8Bytes("info:");
for(int level = 0; level <= 9; level++)
{
for (int strategy : strategies)
{
for (int flush : flushes)
{
deflate(uncompressed,level,true,strategy,flush);
deflate(uncompressed,level,false,strategy,flush);
}
}
}
}
@Test
public void testChrome20_Hello()
{
// Captured from Chrome 20.x - "Hello" (sent from browser/client)
byte rawbuf[] = TypeUtil.fromHexString("c187832b5c11716391d84a2c5c");
assertIncoming(rawbuf,"Hello");
}
@Test
public void testChrome20_Info()
{
// Captured from Chrome 20.x - "info:" (sent from browser/client)
byte rawbuf[] = TypeUtil.fromHexString("c187ca4def7f0081a4b47d4fef");
assertIncoming(rawbuf,"info:");
}
@Test
public void testDeflateBasics() throws Exception
{
// Setup deflater basics
boolean nowrap = true;
Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION,nowrap);
compressor.setStrategy(Deflater.DEFAULT_STRATEGY);
// Text to compress
String text = "info:";
byte uncompressed[] = StringUtil.getUtf8Bytes(text);
// Prime the compressor
compressor.reset();
compressor.setInput(uncompressed,0,uncompressed.length);
compressor.finish();
// Perform compression
ByteBuffer outbuf = ByteBuffer.allocate(64);
BufferUtil.clearToFill(outbuf);
while (!compressor.finished())
{
byte out[] = new byte[64];
int len = compressor.deflate(out,0,out.length,Deflater.SYNC_FLUSH);
if (len > 0)
{
System.err.printf("Compressed %,d bytes%n",len);
outbuf.put(out,0,len);
}
}
compressor.end();
BufferUtil.flipToFlush(outbuf,0);
byte b0 = outbuf.get(0);
if ((b0 & 1) != 0)
{
outbuf.put(0,(b0 ^= 1));
}
byte compressed[] = BufferUtil.toArray(outbuf);
String actual = TypeUtil.toHexString(compressed);
String expected = "CaCc4bCbB70200"; // what pywebsocket produces
// String expected = "CbCc4bCbB70200"; // what java produces
System.out.printf("Compressed data: %s%n",actual);
Assert.assertThat("Compressed data",actual,is(expected));
}
@Test
public void testInflateBasics() throws Exception
{
// should result in "info:" text if properly inflated
byte rawbuf[] = TypeUtil.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces
// byte rawbuf[] = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces
Inflater inflater = new Inflater(true);
inflater.reset();
inflater.setInput(rawbuf,0,rawbuf.length);
byte outbuf[] = new byte[64];
int len = inflater.inflate(outbuf);
inflater.end();
Assert.assertThat("Inflated length",len,greaterThan(4));
String actual = StringUtil.toUTF8String(outbuf,0,len);
Assert.assertThat("Inflated text",actual,is("info:"));
}
@Test
public void testPyWebSocketServer_Hello()
{
// Captured from PyWebSocket - "Hello" (echo from server)
byte rawbuf[] = TypeUtil.fromHexString("c107f248cdc9c90700");
assertIncoming(rawbuf, "Hello");
}
@Test
public void testPyWebSocketServer_Long()
{
// Captured from PyWebSocket - Long Text (echo from server)
byte rawbuf[] = TypeUtil.fromHexString("c1421cca410a80300c44d1abccce9df7" + "f018298634d05631138ab7b7b8fdef1f" + "dc0282e2061d575a45f6f2686bab25e1"
+ "3fb7296fa02b5885eb3b0379c394f461" + "98cafd03");
assertIncoming(rawbuf,"It's a big enough umbrella but it's always me that ends up getting wet.");
}
@Test
public void testPyWebSocketServer_Medium()
{
// Captured from PyWebSocket - "stackoverflow" (echo from server)
byte rawbuf[]=TypeUtil.fromHexString("c10f2a2e494ccece2f4b2d4acbc92f0700");
assertIncoming(rawbuf, "stackoverflow");
}
/**
* Make sure that the server generated compressed form for "Hello" is
* consistent with what PyWebSocket creates.
*/
@Test
public void testServerGeneratedHello() throws IOException
{
assertOutgoing("Hello", "c107f248cdc9c90700");
}
}

View File

@ -0,0 +1,68 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.core.protocol;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.websocket.core.io.OutgoingFrames;
import org.junit.Assert;
/**
* Capture outgoing network bytes.
*/
public class OutgoingNetworkBytesCapture implements OutgoingFrames
{
private final Generator generator;
private List<ByteBuffer> captured;
public OutgoingNetworkBytesCapture(Generator generator)
{
this.generator = generator;
this.captured = new ArrayList<>();
}
public void assertBytes(int idx, String expectedHex)
{
Assert.assertThat("Capture index does not exist",idx,lessThan(captured.size()));
ByteBuffer buf = captured.get(idx);
String actualHex = TypeUtil.toHexString(BufferUtil.toArray(buf)).toUpperCase();
Assert.assertThat("captured[" + idx + "]",actualHex,is(expectedHex.toUpperCase()));
}
public List<ByteBuffer> getCaptured()
{
return captured;
}
@Override
public <C> void output(C context, Callback<C> callback, WebSocketFrame frame) throws IOException
{
ByteBuffer buf = generator.generate(frame);
captured.add(buf.slice());
callback.completed(context);
}
}

View File

@ -58,6 +58,7 @@ public abstract class WebSocketHandler extends HandlerWrapper
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
configurePolicy(policy);
webSocketFactory = new WebSocketServerFactory(policy);
addBean(webSocketFactory);
}
public abstract void configure(WebSocketServerFactory factory);
@ -88,6 +89,7 @@ public abstract class WebSocketHandler extends HandlerWrapper
if (webSocketFactory.acceptWebSocket(request,response))
{
// We have a socket instance created
baseRequest.setHandled(true);
return;
}
// If we reach this point, it means we had an incoming request to upgrade

View File

@ -56,7 +56,6 @@ import org.eclipse.jetty.websocket.core.io.WebSocketSession;
import org.eclipse.jetty.websocket.core.io.event.EventDriver;
import org.eclipse.jetty.websocket.core.io.event.EventDriverFactory;
import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
import org.eclipse.jetty.websocket.server.handshake.HandshakeHixie76;
import org.eclipse.jetty.websocket.server.handshake.HandshakeRFC6455;
/**
@ -69,7 +68,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
private final Map<Integer, WebSocketHandshake> handshakes = new HashMap<>();
{
handshakes.put(HandshakeRFC6455.VERSION,new HandshakeRFC6455());
handshakes.put(HandshakeHixie76.VERSION,new HandshakeHixie76());
// OLD!! handshakes.put(HandshakeHixie76.VERSION,new HandshakeHixie76());
}
private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
@ -364,8 +363,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
Executor executor = http.getConnector().getExecutor();
ByteBufferPool bufferPool = http.getConnector().getByteBufferPool();
WebSocketServerConnection connection = new WebSocketServerConnection(endp,executor,scheduler,driver.getPolicy(),bufferPool,this);
// Tell jetty about the new connection
request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,connection);
LOG.debug("HttpConnection: {}",http);
LOG.debug("AsyncWebSocketConnection: {}",connection);
@ -383,6 +380,9 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// Connect extensions
if (extensions != null)
{
connection.getParser().configureFromExtensions(extensions);
connection.getGenerator().configureFromExtensions(extensions);
Iterator<Extension> extIter;
// Connect outgoings
extIter = extensions.iterator();
@ -391,23 +391,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
// Handle RSV reservations
if (ext.useRsv1())
{
connection.getGenerator().setRsv1InUse(true);
connection.getParser().setRsv1InUse(true);
}
if (ext.useRsv2())
{
connection.getGenerator().setRsv2InUse(true);
connection.getParser().setRsv2InUse(true);
}
if (ext.useRsv3())
{
connection.getGenerator().setRsv3InUse(true);
connection.getParser().setRsv3InUse(true);
}
}
// Connect incomings
@ -426,6 +409,9 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// configure connection for incoming flows
connection.getParser().setIncomingFramesHandler(incoming);
// Tell jetty about the new connection
request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,connection);
// Process (version specific) handshake response
LOG.debug("Handshake Response: {}",handshaker);
handshaker.doHandshakeResponse(request,response);

View File

@ -47,7 +47,7 @@ public class ChromeTest
{
server.stop();
}
@Test
public void testUpgradeWithWebkitDeflateExtension() throws Exception
{
@ -59,7 +59,7 @@ public class ChromeTest
client.connect();
client.sendStandardRequest();
String response = client.expectUpgradeResponse();
Assert.assertThat("Response", response, not(containsString("x-webkit-deflate-frame")));
Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame"));
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.server;
import static org.hamcrest.Matchers.*;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
import org.junit.AfterClass;
@ -25,9 +27,6 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
public class WebSocketInvalidVersionTest
{
private static SimpleServletServer server;
@ -59,7 +58,7 @@ public class WebSocketInvalidVersionTest
client.sendStandardRequest();
String respHeader = client.readResponseHeader();
Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification"));
Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13, 0\r\n"));
Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n"));
}
finally
{

View File

@ -18,6 +18,9 @@
package org.eclipse.jetty.websocket.server.blockhead;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
@ -39,6 +42,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import org.eclipse.jetty.io.ByteBufferPool;
@ -65,12 +69,6 @@ import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.Assert;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
/**
* A simple websocket client for performing unit tests with.
* <p>
@ -219,6 +217,9 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
// Connect extensions
if (extensions != null)
{
generator.configureFromExtensions(extensions);
parser.configureFromExtensions(extensions);
Iterator<Extension> extIter;
// Connect outgoings
extIter = extensions.iterator();
@ -227,20 +228,6 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
// Handle RSV reservations
if (ext.useRsv1())
{
generator.setRsv1InUse(true);
}
if (ext.useRsv2())
{
generator.setRsv2InUse(true);
}
if (ext.useRsv3())
{
generator.setRsv3InUse(true);
}
}
// Connect incomings

View File

@ -0,0 +1,125 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.browser;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.api.UpgradeRequest;
import org.eclipse.jetty.websocket.core.api.UpgradeResponse;
import org.eclipse.jetty.websocket.server.WebSocketCreator;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
/**
* Tool to help debug websocket circumstances reported around browsers.
* <p>
* Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
* websocket implementation from the context of a browser client.
*/
public class BrowserDebugTool implements WebSocketCreator
{
private static final Logger LOG = Log.getLogger(BrowserDebugTool.class);
public static void main(String[] args)
{
int port = 8080;
for (int i = 0; i < args.length; i++)
{
String a = args[i];
if ("-p".equals(a) || "--port".equals(a))
{
port = Integer.parseInt(args[++i]);
}
}
try
{
BrowserDebugTool tool = new BrowserDebugTool();
tool.setupServer(port);
tool.runForever();
}
catch (Throwable t)
{
LOG.warn(t);
}
}
private Server server;
@Override
public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
{
LOG.debug("Creating BrowserSocket");
if (req.getSubProtocols() != null)
{
if (!req.getSubProtocols().isEmpty())
{
String subProtocol = req.getSubProtocols().get(0);
resp.setAcceptedSubProtocol(subProtocol);
}
}
String ua = req.getHeader("User-Agent");
String rexts = req.getHeader("Sec-WebSocket-Extensions");
BrowserSocket socket = new BrowserSocket(ua,rexts);
return socket;
}
private void runForever() throws Exception
{
server.start();
LOG.info("Server available.");
server.join();
}
private void setupServer(int port)
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
WebSocketHandler wsHandler = new WebSocketHandler()
{
@Override
public void configure(WebSocketServerFactory factory)
{
LOG.debug("Configuring WebSocketServerFactory ...");
factory.setCreator(BrowserDebugTool.this);
}
};
server.setHandler(wsHandler);
String resourceBase = "src/test/resources/browser-debug-tool";
ResourceHandler rHandler = new ResourceHandler();
rHandler.setDirectoriesListed(true);
rHandler.setResourceBase(resourceBase);
wsHandler.setHandler(rHandler);
LOG.info("{} setup on port {}",this.getClass().getName(),port);
}
}

View File

@ -0,0 +1,144 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.browser;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.core.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.core.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.core.annotations.WebSocket;
import org.eclipse.jetty.websocket.core.api.WebSocketConnection;
@WebSocket
public class BrowserSocket
{
private static final Logger LOG = Log.getLogger(BrowserSocket.class);
private WebSocketConnection connection;
private final String userAgent;
private final String requestedExtensions;
public BrowserSocket(String ua, String reqExts)
{
this.userAgent = ua;
this.requestedExtensions = reqExts;
}
@OnWebSocketConnect
public void onConnect(WebSocketConnection conn)
{
this.connection = conn;
}
@OnWebSocketClose
public void onDisconnect(int statusCode, String reason)
{
this.connection = null;
LOG.info("Closed [{}, {}]",statusCode,reason);
}
@OnWebSocketMessage
public void onTextMessage(String message)
{
LOG.info("onTextMessage({})",message);
int idx = message.indexOf(':');
if (idx > 0)
{
String key = message.substring(0,idx).toLowerCase();
String val = message.substring(idx + 1);
switch (key)
{
case "info":
{
if (StringUtil.isBlank(userAgent))
{
writeMessage("Client has no User-Agent");
}
else
{
writeMessage("Client User-Agent: " + this.userAgent);
}
if (StringUtil.isBlank(requestedExtensions))
{
writeMessage("Client requested no Sec-WebSocket-Extensions");
}
else
{
writeMessage("Client Sec-WebSocket-Extensions: " + this.requestedExtensions);
}
break;
}
case "time":
{
Calendar now = Calendar.getInstance();
DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
writeMessage("Server time: %s",sdf.format(now.getTime()));
break;
}
default:
{
writeMessage("key[%s] val[%s]",key,val);
}
}
}
else
{
// echo it
writeMessage(message);
}
}
private void writeMessage(String message)
{
if (this.connection == null)
{
LOG.debug("Not connected");
return;
}
if (connection.isOpen() == false)
{
LOG.debug("Not open");
return;
}
try
{
connection.write(null,new FutureCallback<String>(),message);
}
catch (IOException e)
{
LOG.info(e);
}
}
private void writeMessage(String format, Object... args)
{
writeMessage(String.format(format,args));
}
}

View File

@ -0,0 +1,25 @@
<html>
<head>
<title>Jetty WebSocket Browser -&gt; Server Debug Tool</title>
<script type="text/javascript" src="websocket.js"></script>
<link rel="stylesheet" type="text/css" href="main.css" media="all" >
</head>
<body>
jetty websocket/browser/javascript -&gt; server debug tool #console
<div id="console"></div>
<div id="buttons">
<input id="connect" class="button" type="submit" name="connect" value="connect"/>
<input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
<input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
<input id="time" class="button" type="submit" name="time" value="time" disabled="disabled"/>
<input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
</div>
<script type="text/javascript">
$("connect").onclick = function(event) { wstool.connect(); return false; }
$("close").onclick = function(event) {wstool.close(); return false; }
$("info").onclick = function(event) {wstool.write("info:"); return false; }
$("time").onclick = function(event) {wstool.write("time:"); return false; }
$("hello").onclick = function(event) {wstool.write("Hello"); return false; }
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
body {
font-family: sans-serif;
}
div {
border: 0px solid black;
}
div#console {
clear: both;
width: 40em;
height: 20em;
overflow: auto;
background-color: #f0f0f0;
padding: 4px;
border: 1px solid black;
}
div#console .info {
color: black;
}
div#console .client {
color: blue;
}
div#console .server {
color: magenta;
}

View File

@ -0,0 +1,121 @@
if (!window.WebSocket && window.MozWebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (!window.WebSocket) {
alert("WebSocket not supported by this browser");
}
function $() {
return document.getElementById(arguments[0]);
}
function $F() {
return document.getElementById(arguments[0]).value;
}
function getKeyCode(ev) {
if (window.event)
return window.event.keyCode;
return ev.keyCode;
}
var wstool = {
connect : function() {
var location = document.location.toString().replace('http://', 'ws://')
.replace('https://', 'wss://');
wstool.info("Document URI: " + document.location);
wstool.info("WS URI: " + location);
try {
this._ws = new WebSocket(location, "tool");
this._ws.onopen = this._onopen;
this._ws.onmessage = this._onmessage;
this._ws.onclose = this._onclose;
} catch (exception) {
wstool.info("Connect Error: " + exception);
}
},
close : function() {
this._ws.close();
},
_out : function(css, message) {
var console = $('console');
var spanText = document.createElement('span');
spanText.className = 'text ' + css;
spanText.innerHTML = message;
var lineBreak = document.createElement('br');
console.appendChild(spanText);
console.appendChild(lineBreak);
console.scrollTop = console.scrollHeight - console.clientHeight;
},
info : function(message) {
wstool._out("info", message);
},
infoc : function(message) {
wstool._out("client", "[c] " + message);
},
infos : function(message) {
wstool._out("server", "[s] " + message);
},
setState : function(enabled) {
$('connect').disabled = enabled;
$('close').disabled = !enabled;
$('info').disabled = !enabled;
$('time').disabled = !enabled;
$('hello').disabled = !enabled;
},
_onopen : function() {
wstool.setState(true);
wstool.info("Websocket Connected");
},
_send : function(message) {
if (this._ws) {
this._ws.send(message);
wstool.infoc(message);
}
},
write : function(text) {
wstool._send(text);
},
_onmessage : function(m) {
if (m.data) {
wstool.infos(m.data);
}
},
_onclose : function(closeEvent) {
this._ws = null;
wstool.setState(false);
wstool.info("Websocket Closed");
wstool.info(" .wasClean = " + closeEvent.wasClean);
var codeMap = {};
codeMap[1000] = "(NORMAL)";
codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
codeMap[1002] = "(PROTOCOL_ERROR)";
codeMap[1003] = "(UNSUPPORTED_DATA)";
codeMap[1004] = "(UNUSED/RESERVED)";
codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
codeMap[1007] = "(BAD_DATA)";
codeMap[1008] = "(POLICY_VIOLATION)";
codeMap[1009] = "(MESSAGE_TOO_BIG)";
codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
codeMap[1015] = "(INTERNAL/TLS_ERROR)";
var codeStr = codeMap[closeEvent.code];
wstool.info(" .code = " + closeEvent.code + " " + codeStr);
wstool.info(" .reason = " + closeEvent.reason);
}
};

View File

@ -1,5 +1,6 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.LEVEL=INFO
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.io.LEVEL=DEBUG
@ -17,6 +18,9 @@ org.eclipse.jetty.LEVEL=WARN
### See the read/write traffic
# org.eclipse.jetty.websocket.io.Frames.LEVEL=DEBUG
### Show state changes on BrowserDebugTool
org.eclipse.jetty.websocket.server.browser.LEVEL=DEBUG
### Disabling intentional error out of RFCSocket
org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF
### Disable stacks on FrameBytes.failed()