Fixing Frame Decompress to work in Chrome 20.x

This commit is contained in:
Joakim Erdfelt 2012-11-12 20:52:54 -07:00
parent d67fdfdd85
commit 169498cd9d
11 changed files with 96 additions and 20 deletions

View File

@ -24,6 +24,7 @@ import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.eclipse.jetty.util.BufferUtil;
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.api.BadPayloadException;
@ -125,6 +126,9 @@ public class DeflateCompressionMethod implements CompressionMethod
private static class InflaterProcess implements CompressionMethod.Process
{
/** Tail Bytes per Spec */
private static final byte[] TAIL = new byte[]
{ 0x00, 0x00, (byte)0xFF, (byte)0xFF };
private final Inflater inflater;
private int bufferSize = DEFAULT_BUFFER_SIZE;
@ -150,11 +154,16 @@ public class DeflateCompressionMethod implements CompressionMethod
if (LOG.isDebugEnabled())
{
LOG.debug("inflate: {}",BufferUtil.toDetailString(input));
LOG.debug("Input Data: {}",TypeUtil.toHexString(BufferUtil.toArray(input)));
}
// Set the data that is compressed to the inflater
byte compressed[] = BufferUtil.toArray(input);
inflater.setInput(compressed,0,compressed.length);
// Set the data that is compressed (+ TAIL) to the inflater
int len = input.remaining() + 4;
byte raw[] = new byte[len];
int inlen = input.remaining();
input.slice().get(raw,0,inlen);
System.arraycopy(TAIL,0,raw,inlen,TAIL.length);
inflater.setInput(raw,0,raw.length);
}
@Override

View File

@ -65,7 +65,7 @@ public class FrameCompressionExtension extends AbstractExtension
}
// reset on every frame.
method.decompress().end();
// method.decompress().end();
}
finally
{

View File

@ -19,15 +19,15 @@
package org.eclipse.jetty.websocket.common.extensions;
import org.eclipse.jetty.websocket.common.extensions.compress.DeflateCompressionMethodTest;
import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageCompressionExtensionTest;
import org.eclipse.jetty.websocket.common.extensions.compress.WebkitDeflateFrameExtensionTest;
import org.eclipse.jetty.websocket.common.extensions.compress.MessageCompressionExtensionTest;
import org.eclipse.jetty.websocket.common.extensions.compress.FrameCompressionExtensionTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(
{ ExtensionStackTest.class, DeflateCompressionMethodTest.class, PerMessageCompressionExtensionTest.class, FragmentExtensionTest.class,
IdentityExtensionTest.class, WebkitDeflateFrameExtensionTest.class })
{ ExtensionStackTest.class, DeflateCompressionMethodTest.class, MessageCompressionExtensionTest.class, FragmentExtensionTest.class,
IdentityExtensionTest.class, FrameCompressionExtensionTest.class })
public class AllTests
{
/* nothing to do here, its all done in the annotations */

View File

@ -26,10 +26,9 @@ import java.util.List;
import org.eclipse.jetty.util.BufferUtil;
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.common.extensions.compress.CompressionMethod;
import org.eclipse.jetty.websocket.common.extensions.compress.DeflateCompressionMethod;
import org.junit.Assert;
import org.junit.Test;
@ -76,6 +75,50 @@ public class DeflateCompressionMethodTest
Assert.assertEquals("Message Contents",expected,actual);
}
/**
* Test decompression with 2 buffers. First buffer is normal, second relies on back buffers created from first.
*/
@Test
public void testFollowupBackDistance()
{
// The Sample (Compressed) Data
byte buf1[] = TypeUtil.fromHexString("2aC9Cc4dB50200"); // DEFLATE -> "time:"
byte buf2[] = TypeUtil.fromHexString("2a01110000"); // DEFLATE -> "time:"
// Setup Compression Method
CompressionMethod method = new DeflateCompressionMethod();
// Decompressed Data Holder
ByteBuffer decompressed = ByteBuffer.allocate(32);
BufferUtil.flipToFill(decompressed);
// Perform Decompress on Buf 1
BufferUtil.clearToFill(decompressed);
// IGNORE method.decompress().begin();
method.decompress().input(ByteBuffer.wrap(buf1));
while (!method.decompress().isDone())
{
ByteBuffer window = method.decompress().process();
BufferUtil.put(window,decompressed);
}
BufferUtil.flipToFlush(decompressed,0);
LOG.debug("decompressed[1]: {}",BufferUtil.toDetailString(decompressed));
// IGNORE method.decompress().end();
// Perform Decompress on Buf 2
BufferUtil.clearToFill(decompressed);
// IGNORE method.decompress().begin();
method.decompress().input(ByteBuffer.wrap(buf2));
while (!method.decompress().isDone())
{
ByteBuffer window = method.decompress().process();
BufferUtil.put(window,decompressed);
}
BufferUtil.flipToFlush(decompressed,0);
LOG.debug("decompressed[2]: {}",BufferUtil.toDetailString(decompressed));
// IGNORE method.decompress().end();
}
/**
* Test a large payload (a payload length over 65535 bytes).
*

View File

@ -44,7 +44,7 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.junit.Assert;
import org.junit.Test;
public class WebkitDeflateFrameExtensionTest
public class FrameCompressionExtensionTest
{
private void assertIncoming(byte[] raw, String... expectedTextDatas)
{
@ -130,6 +130,16 @@ public class WebkitDeflateFrameExtensionTest
assertIncoming(rawbuf,"info:");
}
@Test
public void testChrome20_TimeTime()
{
// Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser/client)
String time1 = "c1 87 82 46 74 24 a8 8f b8 69 37 44 74".replaceAll("\\s*","");
String time2 = "c1 85 3c fd a1 7f 16 fc b0 7f 3c".replaceAll("\\s*","");
byte rawbuf[] = TypeUtil.fromHexString(time1 + time2);
assertIncoming(rawbuf,"time:","time:");
}
@Test
public void testDeflateBasics() throws Exception
{

View File

@ -41,7 +41,7 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.junit.Assert;
import org.junit.Test;
public class PerMessageCompressionExtensionTest
public class MessageCompressionExtensionTest
{
private void assertDraftExample(String hexStr, String expectedStr)
{

View File

@ -445,11 +445,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
throw new IOException("Unable to start Extension Stack",e);
}
if (LOG.isDebugEnabled())
{
LOG.debug("{}",extensionStack.dump());
}
// Tell jetty about the new connection
request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,connection);

View File

@ -23,7 +23,7 @@ import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(
{ org.eclipse.jetty.websocket.server.ab.AllTests.class, ChromeTest.class, DeflateExtensionTest.class, FragmentExtensionTest.class, IdentityExtensionTest.class,
{ org.eclipse.jetty.websocket.server.ab.AllTests.class, ChromeTest.class, FrameCompressionExtensionTest.class, FragmentExtensionTest.class, IdentityExtensionTest.class,
LoadTest.class, WebSocketInvalidVersionTest.class, WebSocketLoadRFC6455Test.class, WebSocketOverSSLTest.class, WebSocketServletRFCTest.class })
public class AllTests
{

View File

@ -31,7 +31,7 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class DeflateExtensionTest
public class FrameCompressionExtensionTest
{
private static SimpleServletServer server;
@ -68,12 +68,21 @@ public class DeflateExtensionTest
String msg = "Hello";
// Client sends message.
// Client sends first message
client.write(WebSocketFrame.text(msg));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
WebSocketFrame frame = capture.getFrames().get(0);
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
// Client sends second message
client.clearCaptured();
msg = "There";
client.write(WebSocketFrame.text(msg));
capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
frame = capture.getFrames().get(0);
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
}
finally
{

View File

@ -139,6 +139,11 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames
this.extensions.add(xtension);
}
public void clearCaptured()
{
this.incomingFrames.clear();
}
public void clearExtensions()
{
extensions.clear();

View File

@ -73,6 +73,11 @@ public class IncomingFramesCapture implements IncomingFrames
Assert.assertThat("Has no errors",errors.size(),is(0));
}
public void clear()
{
frames.clear();
}
public void dump()
{
System.err.printf("Captured %d incoming frames%n",frames.size());