start of encoder
This commit is contained in:
parent
9e639d9236
commit
fdf73adf39
|
@ -232,6 +232,37 @@ public class HpackContext
|
|||
}
|
||||
}
|
||||
|
||||
public void unuseReferenceSet()
|
||||
{
|
||||
Entry entry = _refSet._refSetNext;
|
||||
while(entry!=_refSet)
|
||||
{
|
||||
entry._used=false;
|
||||
entry=entry._refSetNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void removedUnusedReferences(ByteBuffer buffer)
|
||||
{
|
||||
Entry entry = _refSet._refSetNext;
|
||||
while(entry!=_refSet)
|
||||
{
|
||||
Entry next = entry._refSetNext;
|
||||
|
||||
if (!entry.isUsed())
|
||||
{
|
||||
// encode the reference to remove it
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,index(entry));
|
||||
entry.removeFromRefSet();
|
||||
}
|
||||
entry=next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Iterator<Entry> iterateReferenceSet()
|
||||
{
|
||||
return new Iterator<Entry>()
|
||||
|
@ -350,11 +381,11 @@ public class HpackContext
|
|||
/* ------------------------------------------------------------ */
|
||||
public static class Entry
|
||||
{
|
||||
int _index;
|
||||
final HttpField _field;
|
||||
int _index;
|
||||
Entry _refSetNext=this;
|
||||
Entry _refSetPrev=this;
|
||||
boolean _refSetUsed;
|
||||
boolean _used;
|
||||
|
||||
Entry()
|
||||
{
|
||||
|
@ -382,7 +413,8 @@ public class HpackContext
|
|||
throw new IllegalStateException("evicted");
|
||||
if (_refSetNext!=this)
|
||||
return;
|
||||
|
||||
|
||||
_used=true;
|
||||
_refSetNext=ctx._refSet;
|
||||
_refSetPrev=ctx._refSet._refSetPrev;
|
||||
ctx._refSet._refSetPrev._refSetNext=this;
|
||||
|
@ -402,6 +434,7 @@ public class HpackContext
|
|||
_refSetPrev._refSetNext=_refSetNext;
|
||||
_refSetNext=this;
|
||||
_refSetPrev=this;
|
||||
_used=false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,6 +462,18 @@ public class HpackContext
|
|||
{
|
||||
return String.format("{%s,%d,%s,%x}",isStatic()?"S":"D",_index,_field,hashCode());
|
||||
}
|
||||
|
||||
public void used()
|
||||
{
|
||||
_used=true;
|
||||
}
|
||||
|
||||
public boolean isUsed()
|
||||
{
|
||||
return _used;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class StaticEntry extends Entry
|
||||
|
|
|
@ -20,16 +20,49 @@
|
|||
package org.eclipse.jetty.hpack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.eclipse.jetty.hpack.HpackContext.Entry;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
|
||||
public class HpackEncoder
|
||||
{
|
||||
private final static EnumSet<HttpHeader> __NEVER_INDEX =
|
||||
EnumSet.of(HttpHeader.SET_COOKIE,
|
||||
HttpHeader.SET_COOKIE2);
|
||||
|
||||
private final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
|
||||
EnumSet.of(HttpHeader.COOKIE,
|
||||
HttpHeader.SET_COOKIE,
|
||||
HttpHeader.SET_COOKIE2,
|
||||
HttpHeader.AUTHORIZATION,
|
||||
HttpHeader.CONTENT_MD5,
|
||||
HttpHeader.PROXY_AUTHENTICATE,
|
||||
HttpHeader.PROXY_AUTHORIZATION);
|
||||
|
||||
private final static EnumSet<HttpHeader> __USE_REFERENCE_SET =
|
||||
EnumSet.of(HttpHeader.ACCEPT,
|
||||
HttpHeader.ACCEPT_CHARSET,
|
||||
HttpHeader.ACCEPT_ENCODING,
|
||||
HttpHeader.ACCEPT_LANGUAGE,
|
||||
HttpHeader.ACCEPT_RANGES,
|
||||
HttpHeader.ALLOW,
|
||||
HttpHeader.AUTHORIZATION,
|
||||
HttpHeader.CACHE_CONTROL,
|
||||
HttpHeader.CONTENT_LANGUAGE,
|
||||
HttpHeader.COOKIE,
|
||||
HttpHeader.DATE,
|
||||
HttpHeader.HOST,
|
||||
HttpHeader.SERVER,
|
||||
HttpHeader.SERVLET_ENGINE,
|
||||
HttpHeader.USER_AGENT);
|
||||
|
||||
private final ByteBufferPool _byteBufferPool;
|
||||
private final HpackContext _context;
|
||||
|
||||
|
||||
public HpackEncoder(ByteBufferPool byteBufferPool)
|
||||
{
|
||||
|
@ -41,15 +74,38 @@ public class HpackEncoder
|
|||
this._byteBufferPool = byteBufferPool;
|
||||
_context=new HpackContext(maxHeaderTableSize);
|
||||
}
|
||||
|
||||
public HpackContext getContext()
|
||||
{
|
||||
return _context;
|
||||
}
|
||||
|
||||
/*
|
||||
public ByteBufferPool.Lease encode(HttpFields fields)
|
||||
{
|
||||
return new ByteBufferPool.Lease(_byteBufferPool);
|
||||
}
|
||||
|
||||
|
||||
private boolean encode(ByteBuffer buffer, HttpField field)
|
||||
*/
|
||||
|
||||
public void encode(ByteBuffer buffer, HttpFields fields)
|
||||
{
|
||||
// Clear the used bits
|
||||
_context.unuseReferenceSet();
|
||||
|
||||
// Add all the known fields
|
||||
for (HttpField field : fields)
|
||||
{
|
||||
encode(buffer,field);
|
||||
}
|
||||
|
||||
_context.removedUnusedReferences(buffer);
|
||||
}
|
||||
|
||||
private void encode(ByteBuffer buffer, HttpField field)
|
||||
{
|
||||
// TODO currently we do not check if there is enough space, so we will always
|
||||
// return true or fail nastily.
|
||||
|
||||
// Is there an entry for the field?
|
||||
Entry entry = _context.get(field);
|
||||
|
||||
|
@ -57,12 +113,15 @@ public class HpackEncoder
|
|||
{
|
||||
// if entry is already in the reference set, then nothing more to do.
|
||||
if (entry.isInReferenceSet())
|
||||
return true;
|
||||
{
|
||||
entry.used();
|
||||
return;
|
||||
}
|
||||
|
||||
// Is this as static field
|
||||
if (entry.isStatic())
|
||||
{
|
||||
// TODO Policy decision to make!
|
||||
// TODO Strategy 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
|
||||
|
@ -77,9 +136,107 @@ public class HpackEncoder
|
|||
// Add the value
|
||||
buffer.put(entry.getStaticHuffmanValue());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// So we can add the entry to the reference Set and emit the index;
|
||||
_context.addToRefSet(entry);
|
||||
int index=_context.index(entry);
|
||||
|
||||
// TODO pregenerate indexes?
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Must be a new entry, so we will have to send literally.
|
||||
// TODO Strategy decision to make!
|
||||
// What fields will we put in the reference set and what fields will we huffman encode?
|
||||
|
||||
// Let's make these decisions by lookup of known fields
|
||||
HttpHeader header = field.getHeader();
|
||||
final boolean never_index;
|
||||
final boolean huffman;
|
||||
final boolean reference;
|
||||
final int name_bits;
|
||||
final byte mask;
|
||||
if (header==null)
|
||||
{
|
||||
never_index=false;
|
||||
huffman=true;
|
||||
reference=true;
|
||||
name_bits = 6;
|
||||
mask=(byte)0x40;
|
||||
}
|
||||
else if (__USE_REFERENCE_SET.contains(header))
|
||||
{
|
||||
reference=true;
|
||||
never_index=false;
|
||||
huffman=__DO_NOT_HUFFMAN.contains(header);
|
||||
name_bits = 6;
|
||||
mask=(byte)0x40;
|
||||
}
|
||||
else
|
||||
{
|
||||
reference=false;
|
||||
never_index=__NEVER_INDEX.contains(header);
|
||||
huffman=__DO_NOT_HUFFMAN.contains(header);
|
||||
name_bits = 4;
|
||||
mask=never_index?(byte)0x01:(byte)0x00;
|
||||
}
|
||||
|
||||
|
||||
// Add the mask bits
|
||||
buffer.put(mask);
|
||||
|
||||
// Look for a name Index
|
||||
Entry name_entry = _context.get(field.getName());
|
||||
if (name_entry!=null)
|
||||
NBitInteger.encode(buffer,name_bits,_context.index(name_entry));
|
||||
else
|
||||
{
|
||||
// Encode the name always with huffman
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,Huffman.octetsNeeded(field.getName()));
|
||||
Huffman.encode(buffer,field.getName());
|
||||
}
|
||||
|
||||
// Add the literal value
|
||||
String value=field.getValue();
|
||||
if (huffman)
|
||||
{
|
||||
// huffman literal value
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,Huffman.octetsNeeded(value));
|
||||
Huffman.encode(buffer,field.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
// add literal assuming iso_8859_1
|
||||
buffer.put((byte)0x80);
|
||||
NBitInteger.encode(buffer,7,value.length());
|
||||
for (int i=0;i<value.length();i++)
|
||||
{
|
||||
char c=value.charAt(i);
|
||||
if (c<' '|| c>127)
|
||||
throw new IllegalArgumentException();
|
||||
buffer.put((byte)c);
|
||||
}
|
||||
}
|
||||
|
||||
// If we want the field referenced, then we add it to our
|
||||
// table and reference set.
|
||||
if (reference)
|
||||
{
|
||||
Entry new_entry=_context.add(field);
|
||||
if (new_entry!=null)
|
||||
_context.addToRefSet(new_entry);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.hpack;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.eclipse.jetty.hpack.HpackContext.Entry;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
public class HpackEncoderTest
|
||||
{
|
||||
@Test
|
||||
public void testUnknownFieldsContextManagement()
|
||||
{
|
||||
HpackEncoder encoder = new HpackEncoder(null,38*5);
|
||||
HttpFields fields = new HttpFields();
|
||||
|
||||
|
||||
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"),
|
||||
};
|
||||
|
||||
// Add 4 entries
|
||||
for (int i=0;i<=3;i++)
|
||||
fields.add(field[i]);
|
||||
|
||||
// encode them
|
||||
ByteBuffer buffer = BufferUtil.allocate(4096);
|
||||
int pos = BufferUtil.flipToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,pos);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// All are in the header table
|
||||
Assert.assertEquals(4,encoder.getContext().size());
|
||||
|
||||
// All are in the reference set
|
||||
HashSet<HttpField> refSet = new HashSet<>();
|
||||
for (Entry entry : encoder.getContext().getReferenceSet())
|
||||
refSet.add(entry.getHttpField());
|
||||
Assert.assertEquals(4,refSet.size());
|
||||
for (int i=0;i<=3;i++)
|
||||
Assert.assertTrue(refSet.contains(field[i]));
|
||||
|
||||
// encode exact same fields again!
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// nothing should be encoded!
|
||||
assertThat(buffer.remaining(),Matchers.is(0));
|
||||
|
||||
// All are in the header table
|
||||
Assert.assertEquals(4,encoder.getContext().size());
|
||||
|
||||
// All are in the reference set
|
||||
refSet.clear();
|
||||
for (Entry entry : encoder.getContext().getReferenceSet())
|
||||
refSet.add(entry.getHttpField());
|
||||
Assert.assertEquals(4,refSet.size());
|
||||
for (int i=0;i<=3;i++)
|
||||
Assert.assertTrue(refSet.contains(field[i]));
|
||||
|
||||
// Add 4 more fields
|
||||
for (int i=4;i<=7;i++)
|
||||
fields.add(field[i]);
|
||||
|
||||
// encode
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// max header table size reached
|
||||
Assert.assertEquals(5,encoder.getContext().size());
|
||||
|
||||
// last 5 in reference set
|
||||
refSet.clear();
|
||||
for (Entry entry : encoder.getContext().getReferenceSet())
|
||||
refSet.add(entry.getHttpField());
|
||||
Assert.assertEquals(5,refSet.size());
|
||||
for (int i=3;i<=7;i++)
|
||||
Assert.assertTrue(refSet.contains(field[i]));
|
||||
|
||||
|
||||
// remove some fields
|
||||
for (int i=0;i<=7;i+=2)
|
||||
fields.remove(field[i].getName());
|
||||
|
||||
// encode
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// max header table size reached
|
||||
Assert.assertEquals(5,encoder.getContext().size());
|
||||
|
||||
// last 5 in reference set
|
||||
refSet.clear();
|
||||
for (Entry entry : encoder.getContext().getReferenceSet())
|
||||
refSet.add(entry.getHttpField());
|
||||
Assert.assertEquals(4,refSet.size());
|
||||
for (int i=0;i<=7;i++)
|
||||
{
|
||||
if (i%2==1)
|
||||
Assert.assertTrue(refSet.contains(field[i]));
|
||||
else
|
||||
Assert.assertFalse(refSet.contains(field[i]));
|
||||
}
|
||||
|
||||
// remove another fields
|
||||
fields.remove(field[1].getName());
|
||||
|
||||
// encode
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// max header table size reached
|
||||
Assert.assertEquals(5,encoder.getContext().size());
|
||||
|
||||
// last 5 in reference set
|
||||
refSet.clear();
|
||||
for (Entry entry : encoder.getContext().getReferenceSet())
|
||||
refSet.add(entry.getHttpField());
|
||||
Assert.assertEquals(3,refSet.size());
|
||||
for (int i=2;i<=7;i++)
|
||||
{
|
||||
if (i%2==1)
|
||||
Assert.assertTrue(refSet.contains(field[i]));
|
||||
else
|
||||
Assert.assertFalse(refSet.contains(field[i]));
|
||||
}
|
||||
|
||||
// re add the field
|
||||
|
||||
fields.add(field[1]);
|
||||
|
||||
// encode
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// max header table size reached
|
||||
Assert.assertEquals(5,encoder.getContext().size());
|
||||
|
||||
// last 5 in reference set
|
||||
refSet.clear();
|
||||
for (Entry entry : encoder.getContext().getReferenceSet())
|
||||
refSet.add(entry.getHttpField());
|
||||
Assert.assertEquals(4,refSet.size());
|
||||
for (int i=0;i<=7;i++)
|
||||
{
|
||||
if (i%2==1)
|
||||
Assert.assertTrue(refSet.contains(field[i]));
|
||||
else
|
||||
Assert.assertFalse(refSet.contains(field[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotReferenceStatics()
|
||||
{
|
||||
HpackEncoder encoder = new HpackEncoder(null,38*5);
|
||||
ByteBuffer buffer = BufferUtil.allocate(4096);
|
||||
|
||||
HttpFields fields = new HttpFields();
|
||||
fields.put(":method","POST");
|
||||
|
||||
// encode
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// empty header table
|
||||
Assert.assertEquals(0,encoder.getContext().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeverIndexSetCookie()
|
||||
{
|
||||
HpackEncoder encoder = new HpackEncoder(null,38*5);
|
||||
ByteBuffer buffer = BufferUtil.allocate(4096);
|
||||
|
||||
HttpFields fields = new HttpFields();
|
||||
fields.put("set-cookie","some cookie value");
|
||||
|
||||
// encode
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// empty header table
|
||||
Assert.assertEquals(0,encoder.getContext().size());
|
||||
|
||||
|
||||
// encode again
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer,fields);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
|
||||
// something was encoded!
|
||||
assertThat(buffer.remaining(),Matchers.greaterThan(0));
|
||||
|
||||
// empty header table
|
||||
Assert.assertEquals(0,encoder.getContext().size());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue