encoded static field

This commit is contained in:
Greg Wilkins 2014-06-08 17:06:18 +02:00
parent ae4dea3e1e
commit 9e639d9236
4 changed files with 210 additions and 27 deletions

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.hpack; package org.eclipse.jetty.hpack;
import java.nio.ByteBuffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -109,7 +110,7 @@ public class HpackContext
Set<String> added = new HashSet<>(); Set<String> added = new HashSet<>();
for (int i=1;i<STATIC_TABLE.length;i++) for (int i=1;i<STATIC_TABLE.length;i++)
{ {
Entry entry=__staticTable[i]=new Entry(i,STATIC_TABLE[i][0],STATIC_TABLE[i][1],true); Entry entry=__staticTable[i]=new StaticEntry(i,STATIC_TABLE[i][0],STATIC_TABLE[i][1]);
if (entry._field.getValue()!=null) if (entry._field.getValue()!=null)
__staticFieldMap.put(entry._field,entry); __staticFieldMap.put(entry._field,entry);
if (!added.contains(entry._field.getName())) if (!added.contains(entry._field.getName()))
@ -122,7 +123,7 @@ public class HpackContext
private int _maxHeaderTableSizeInBytes; private int _maxHeaderTableSizeInBytes;
private int _headerTableSizeInBytes; private int _headerTableSizeInBytes;
private final Entry _refSet=new Entry(true); private final Entry _refSet=new Entry();
private final HeaderTable _headerTable; private final HeaderTable _headerTable;
private final Map<HttpField,Entry> _fieldMap = new HashMap<>(); private final Map<HttpField,Entry> _fieldMap = new HashMap<>();
private final Map<String,Entry> _nameMap = new HashMap<>(); private final Map<String,Entry> _nameMap = new HashMap<>();
@ -183,7 +184,7 @@ public class HpackContext
public Entry add(HttpField field) public Entry add(HttpField field)
{ {
int i=_headerTable.getNextIndexUnsafe(); int i=_headerTable.getNextIndexUnsafe();
Entry entry=new Entry(i,field,false); Entry entry=new Entry(i,field);
int size = entry.getSize(); int size = entry.getSize();
if (size>_maxHeaderTableSizeInBytes) if (size>_maxHeaderTableSizeInBytes)
return null; return null;
@ -220,6 +221,17 @@ public class HpackContext
return referenceSet; return referenceSet;
} }
public void clearReferenceSet()
{
Entry entry = _refSet._refSetNext;
while(entry!=_refSet)
{
Entry next = entry._refSetNext;
entry.removeFromRefSet();
entry=next;
}
}
public Iterator<Entry> iterateReferenceSet() public Iterator<Entry> iterateReferenceSet()
{ {
return new Iterator<Entry>() return new Iterator<Entry>()
@ -339,36 +351,32 @@ public class HpackContext
public static class Entry public static class Entry
{ {
int _index; int _index;
final boolean _static;
final HttpField _field; final HttpField _field;
Entry _refSetNext=this; Entry _refSetNext=this;
Entry _refSetPrev=this; Entry _refSetPrev=this;
boolean _refSetUsed; boolean _refSetUsed;
Entry(boolean isStatic) Entry()
{ {
_static=isStatic;
_index=0; _index=0;
_field=null; _field=null;
} }
Entry(int index,String name, String value, boolean isStatic) Entry(int index,String name, String value)
{ {
_static=isStatic;
_index=index; _index=index;
_field=new HttpField(name,value); _field=new HttpField(name,value);
} }
Entry(int index, HttpField field, boolean isStatic) Entry(int index, HttpField field)
{ {
_static=isStatic;
_index=index; _index=index;
_field=field; _field=field;
} }
private void addToRefSet(HpackContext ctx) private void addToRefSet(HpackContext ctx)
{ {
if (_static) if (isStatic())
throw new IllegalStateException("static"); throw new IllegalStateException("static");
if (_index<0) if (_index<0)
throw new IllegalStateException("evicted"); throw new IllegalStateException("evicted");
@ -381,6 +389,11 @@ public class HpackContext
ctx._refSet._refSetPrev=this; ctx._refSet._refSetPrev=this;
} }
public boolean isInReferenceSet()
{
return _refSetNext!=this;
}
public void removeFromRefSet() public void removeFromRefSet()
{ {
if (_refSetNext!=this) if (_refSetNext!=this)
@ -397,25 +410,63 @@ public class HpackContext
return 32+_field.getName().length()+_field.getValue().length(); return 32+_field.getName().length()+_field.getValue().length();
} }
/**
* @return
*/
public HttpField getHttpField() public HttpField getHttpField()
{ {
return _field; return _field;
} }
/**
* @return
*/
public boolean isStatic() public boolean isStatic()
{ {
return _static; return false;
}
public byte[] getStaticHuffmanValue()
{
return null;
} }
public String toString() public String toString()
{ {
return String.format("{%s,%d,%s,%x}",_static?"S":"D",_index,_field,hashCode()); return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_index,_field,hashCode());
}
}
public static class StaticEntry extends Entry
{
final byte[] _huffmanValue;
StaticEntry(int index,String name, String value)
{
super(index,name,value);
if (value!=null && value.length()>0)
{
int huffmanLen = Huffman.octetsNeeded(value);
int lenLen = NBitInteger.octectsNeeded(7,huffmanLen);
_huffmanValue = new byte[1+lenLen+huffmanLen];
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
// Indicate Huffman
buffer.put((byte)0x80);
// Add huffman length
NBitInteger.encode(buffer,7,huffmanLen);
// Encode value
Huffman.encode(buffer,value);
}
else
_huffmanValue=null;
}
@Override
public boolean isStatic()
{
return true;
}
@Override
public byte[] getStaticHuffmanValue()
{
return _huffmanValue;
} }
} }

View File

@ -19,20 +19,68 @@
package org.eclipse.jetty.hpack; package org.eclipse.jetty.hpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.hpack.HpackContext.Entry;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
public class HpackEncoder public class HpackEncoder
{ {
private final ByteBufferPool byteBufferPool; private final ByteBufferPool _byteBufferPool;
private final HpackContext _context;
public HpackEncoder(ByteBufferPool byteBufferPool) public HpackEncoder(ByteBufferPool byteBufferPool)
{ {
this.byteBufferPool = byteBufferPool; this(byteBufferPool,4096);
}
public HpackEncoder(ByteBufferPool byteBufferPool,int maxHeaderTableSize)
{
this._byteBufferPool = byteBufferPool;
_context=new HpackContext(maxHeaderTableSize);
} }
public ByteBufferPool.Lease encode(HttpFields fields) public ByteBufferPool.Lease encode(HttpFields fields)
{ {
return new ByteBufferPool.Lease(byteBufferPool); return new ByteBufferPool.Lease(_byteBufferPool);
}
private boolean encode(ByteBuffer buffer, HttpField field)
{
// Is there an entry for the field?
Entry entry = _context.get(field);
if (entry!=null)
{
// if entry is already in the reference set, then nothing more to do.
if (entry.isInReferenceSet())
return true;
// Is this as static field
if (entry.isStatic())
{
// TODO Policy decision to make!
// Should we add to reference set or just always send as indexed?
// Let's always send as indexed to reduce churn in header table!
// BUGGER! Can't do that. Oh well all the static fields have small values, so
// lets send as literal header, indexed name.
// We don't need never indexed because the cookie fields are name only and we can
// huffman encode the value for the same reason.
// Add the token
buffer.put((byte)0x00);
// Add the name index
NBitInteger.encode(buffer,4,_context.index(entry));
// Add the value
buffer.put(entry.getStaticHuffmanValue());
} }
}
}
} }

View File

@ -341,7 +341,7 @@ public class Huffman
} }
static public String decode(ByteBuffer buffer) throws IOException static public String decode(ByteBuffer buffer)
{ {
StringBuilder out = new StringBuilder(buffer.remaining()*2); StringBuilder out = new StringBuilder(buffer.remaining()*2);
int node = 0; int node = 0;

View File

@ -26,6 +26,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@ -398,6 +399,13 @@ public class HpackContextTest
ctx.addToRefSet(ctx.get(1)); ctx.addToRefSet(ctx.get(1));
ctx.addToRefSet(ctx.get(5)); ctx.addToRefSet(ctx.get(5));
// check isInReferenceSet
assertTrue(ctx.get(1).isInReferenceSet());
assertFalse(ctx.get(2).isInReferenceSet());
assertTrue(ctx.get(3).isInReferenceSet());
assertFalse(ctx.get(4).isInReferenceSet());
assertTrue(ctx.get(5).isInReferenceSet());
// iterate ref set // iterate ref set
HashSet<HttpField> fields = new HashSet<>(); HashSet<HttpField> fields = new HashSet<>();
for (Entry e: ctx.getReferenceSet() ) for (Entry e: ctx.getReferenceSet() )
@ -426,6 +434,13 @@ public class HpackContextTest
assertTrue(fields.contains(field[0])); assertTrue(fields.contains(field[0]));
assertTrue(fields.contains(field[4])); assertTrue(fields.contains(field[4]));
// check isInReferenceSet
assertTrue(ctx.get(1).isInReferenceSet());
assertFalse(ctx.get(2).isInReferenceSet());
assertFalse(ctx.get(3).isInReferenceSet());
assertFalse(ctx.get(4).isInReferenceSet());
assertTrue(ctx.get(5).isInReferenceSet());
// iterator remove // iterator remove
Iterator<Entry> iter=ctx.iterateReferenceSet(); Iterator<Entry> iter=ctx.iterateReferenceSet();
iter.next(); iter.next();
@ -445,6 +460,55 @@ public class HpackContextTest
} }
@Test
public void testRefSetClear()
{
// Only enough space for 5 entries
HpackContext ctx = new HpackContext(38*5);
HttpField[] field =
{
new HttpField("fo0","b0r"),
new HttpField("fo1","b1r"),
new HttpField("fo2","b2r"),
new HttpField("fo3","b3r"),
new HttpField("fo4","b4r"),
new HttpField("fo5","b5r"),
new HttpField("fo6","b6r"),
new HttpField("fo7","b7r"),
new HttpField("fo8","b8r"),
new HttpField("fo9","b9r"),
new HttpField("foA","bAr"),
};
Entry[] entry = new Entry[field.length];
// Add 5 entries
for (int i=0;i<=4;i++)
entry[i]=ctx.add(field[i]);
// Add 3 entries to reference set
ctx.clearReferenceSet();
ctx.addToRefSet(ctx.get(3));
ctx.addToRefSet(ctx.get(1));
ctx.addToRefSet(ctx.get(5));
// iterate ref set
HashSet<HttpField> fields = new HashSet<>();
for (Entry e: ctx.getReferenceSet() )
fields.add(e.getHttpField());
assertEquals(3,fields.size());
assertTrue(fields.contains(field[0]));
assertTrue(fields.contains(field[2]));
assertTrue(fields.contains(field[4]));
// Clear set
ctx.clearReferenceSet();
fields.clear();
for (Entry e: ctx.getReferenceSet() )
fields.add(e.getHttpField());
assertEquals(0,fields.size());
}
@Test @Test
public void testResize() public void testResize()
{ {
@ -598,8 +662,28 @@ public class HpackContextTest
assertFalse(fields.contains(field[0])); assertFalse(fields.contains(field[0]));
assertFalse(fields.contains(field[2])); assertFalse(fields.contains(field[2]));
assertTrue(fields.contains(field[4])); assertTrue(fields.contains(field[4]));
}
@Test
public void testStaticHuffmanValues()
{
HpackContext ctx = new HpackContext(4096);
for (int i=2;i<=14;i++)
{
Entry entry=ctx.get(i);
assertTrue(entry.isStatic());
ByteBuffer buffer = ByteBuffer.wrap(entry.getStaticHuffmanValue());
int huff = 0xff&buffer.get();
assertTrue((0x80&huff)==0x80);
int len = NBitInteger.decode(buffer,7);
assertEquals(len,buffer.remaining());
String value = Huffman.decode(buffer);
assertEquals(entry.getHttpField().getValue(),value);
} }
} }
}