added logging to hpack

This commit is contained in:
Greg Wilkins 2014-06-10 15:26:54 +02:00
parent 073ad924b0
commit c8184077c7
5 changed files with 178 additions and 133 deletions

View File

@ -31,12 +31,15 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.ArrayQueue; import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HpackContext public class HpackContext
{ {
public static final Logger LOG = Log.getLogger(HpackContext.class);
public static final String[][] STATIC_TABLE = public static final String[][] STATIC_TABLE =
{ {
{null,null}, {null,null},
@ -182,6 +185,7 @@ public class HpackContext
public void resize(int maxHeaderTableSize) public void resize(int maxHeaderTableSize)
{ {
LOG.debug("HdrTbl resized {}",maxHeaderTableSize);
_maxHeaderTableSizeInBytes=maxHeaderTableSize; _maxHeaderTableSizeInBytes=maxHeaderTableSize;
int guesstimateEntries = 10+maxHeaderTableSize/(32+10+10); int guesstimateEntries = 10+maxHeaderTableSize/(32+10+10);
evict(); evict();
@ -223,12 +227,16 @@ public class HpackContext
Entry entry=new Entry(i,field); Entry entry=new Entry(i,field);
int size = entry.getSize(); int size = entry.getSize();
if (size>_maxHeaderTableSizeInBytes) if (size>_maxHeaderTableSizeInBytes)
{
LOG.debug("!added {} too big",field);
return null; return null;
}
_headerTableSizeInBytes+=size; _headerTableSizeInBytes+=size;
_headerTable.addUnsafe(entry); _headerTable.addUnsafe(entry);
_fieldMap.put(field,entry); _fieldMap.put(field,entry);
_nameMap.put(StringUtil.asciiToLowerCase(field.getName()),entry); _nameMap.put(StringUtil.asciiToLowerCase(field.getName()),entry);
LOG.debug("HdrTbl added {}",entry);
evict(); evict();
return entry; return entry;
} }
@ -249,6 +257,7 @@ public class HpackContext
public void addToRefSet(Entry entry) public void addToRefSet(Entry entry)
{ {
LOG.debug("RefSet added {}",entry);
entry.addToRefSet(this); entry.addToRefSet(this);
} }
@ -259,6 +268,7 @@ public class HpackContext
public void clearReferenceSet() public void clearReferenceSet()
{ {
LOG.debug("RefSet cleared");
Entry entry = _refSet._refSetNext; Entry entry = _refSet._refSetNext;
while(entry!=_refSet) while(entry!=_refSet)
{ {
@ -280,6 +290,7 @@ public class HpackContext
entry._used=false; entry._used=false;
else else
{ {
LOG.debug("RefSet remove unused {}",entry);
// encode the reference to remove it // encode the reference to remove it
buffer.put((byte)0x80); buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index(entry)); NBitInteger.encode(buffer,7,index(entry));
@ -297,7 +308,10 @@ public class HpackContext
if (entry.isUsed()) if (entry.isUsed())
entry._used=false; entry._used=false;
else else
{
LOG.debug("RefSet emit unused {}",entry);
builder.emit(entry.getHttpField()); builder.emit(entry.getHttpField());
}
entry=entry._refSetNext; entry=entry._refSetNext;
} }
@ -342,6 +356,7 @@ public class HpackContext
while (_headerTableSizeInBytes>_maxHeaderTableSizeInBytes) while (_headerTableSizeInBytes>_maxHeaderTableSizeInBytes)
{ {
Entry entry = _headerTable.pollUnsafe(); Entry entry = _headerTable.pollUnsafe();
LOG.debug("HdrTbl evict {}",entry);
_headerTableSizeInBytes-=entry.getSize(); _headerTableSizeInBytes-=entry.getSize();
entry.removeFromRefSet(); entry.removeFromRefSet();
entry._index=-1; entry._index=-1;
@ -460,6 +475,7 @@ public class HpackContext
_refSetPrev=ctx._refSet._refSetPrev; _refSetPrev=ctx._refSet._refSetPrev;
ctx._refSet._refSetPrev._refSetNext=this; ctx._refSet._refSetPrev._refSetNext=this;
ctx._refSet._refSetPrev=this; ctx._refSet._refSetPrev=this;
LOG.debug("RefSet add {}",this);
} }
public boolean isInReferenceSet() public boolean isInReferenceSet()
@ -469,6 +485,7 @@ public class HpackContext
public void removeFromRefSet() public void removeFromRefSet()
{ {
LOG.debug("RefSet remove {}",this);
if (_refSetNext!=this) if (_refSetNext!=this)
{ {
_refSetNext._refSetPrev=_refSetPrev; _refSetNext._refSetPrev=_refSetPrev;

View File

@ -26,6 +26,9 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -34,6 +37,8 @@ import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
*/ */
public class HpackDecoder public class HpackDecoder
{ {
public static final Logger LOG = Log.getLogger(HpackDecoder.class);
private final HpackContext _context; private final HpackContext _context;
private final MetaDataBuilder _builder = new MetaDataBuilder(); private final MetaDataBuilder _builder = new MetaDataBuilder();
@ -48,10 +53,15 @@ public class HpackDecoder
} }
public MetaData decode(ByteBuffer buffer) public MetaData decode(ByteBuffer buffer)
{ {
while(buffer.hasRemaining()) while(buffer.hasRemaining())
{ {
if (LOG.isDebugEnabled())
{
int l=Math.min(buffer.remaining(),16);
LOG.debug("decode "+TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+buffer.position(),l)+(l<buffer.remaining()?"...":""));
}
byte b = buffer.get(); byte b = buffer.get();
if (b<0) if (b<0)
{ {
@ -62,6 +72,7 @@ public class HpackDecoder
_context.get(index).removeFromRefSet(); _context.get(index).removeFromRefSet();
else if (entry.isStatic()) else if (entry.isStatic())
{ {
LOG.debug("decode IdxStatic {}",entry);
// emit field // emit field
_builder.emit(entry.getHttpField()); _builder.emit(entry.getHttpField());
@ -72,6 +83,7 @@ public class HpackDecoder
} }
else else
{ {
LOG.debug("decode Idx {}",entry);
// emit // emit
_builder.emit(entry.getHttpField()); _builder.emit(entry.getHttpField());
// add to reference set // add to reference set
@ -91,6 +103,7 @@ public class HpackDecoder
// literal // literal
boolean indexed=f>=4; boolean indexed=f>=4;
int bits=indexed?6:4; int bits=indexed?6:4;
boolean huffmanName=false;
// decode the name // decode the name
int name_index=NBitInteger.decode(buffer,bits); int name_index=NBitInteger.decode(buffer,bits);
@ -102,9 +115,9 @@ public class HpackDecoder
} }
else else
{ {
boolean huffman = (buffer.get()&0x80)==0x80; huffmanName = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7); int length = NBitInteger.decode(buffer,7);
if (huffman) if (huffmanName)
name=Huffman.decode(buffer,length); name=Huffman.decode(buffer,length);
else else
name=toASCIIString(buffer,length); name=toASCIIString(buffer,length);
@ -112,9 +125,9 @@ public class HpackDecoder
} }
// decode the value // decode the value
boolean huffman = (buffer.get()&0x80)==0x80; boolean huffmanValue = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7); int length = NBitInteger.decode(buffer,7);
if (huffman) if (huffmanValue)
value=Huffman.decode(buffer,length); value=Huffman.decode(buffer,length);
else else
value=toASCIIString(buffer,length); value=toASCIIString(buffer,length);
@ -132,7 +145,7 @@ public class HpackDecoder
break; break;
case ":status": case ":status":
Integer code = Integer.getInteger(value); Integer code = Integer.valueOf(value);
field = new StaticValueHttpField(header,name,value,code); field = new StaticValueHttpField(header,name,value,code);
break; break;
@ -153,6 +166,9 @@ public class HpackDecoder
break; break;
} }
if (LOG.isDebugEnabled())
LOG.debug("decoded '"+field+"' by Lit"+(name_index>0?"IdxName":(huffmanName?"HuffName":"LitName"))+(huffmanValue?"HuffVal":"LitVal")+(indexed?"Idx":""));
// emit the field // emit the field
_builder.emit(field); _builder.emit(field);
@ -170,11 +186,14 @@ public class HpackDecoder
{ {
// change table size // change table size
int size = NBitInteger.decode(buffer,4); int size = NBitInteger.decode(buffer,4);
if (LOG.isDebugEnabled())
LOG.debug("decode resize="+size);
_context.resize(size); _context.resize(size);
} }
else if (f==3) else if (f==3)
{ {
// clear reference set // clear reference set
LOG.debug("decode clear");
_context.clearReferenceSet(); _context.clearReferenceSet();
} }
} }

View File

@ -29,9 +29,13 @@ import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.io.ByteBufferPool.Lease; import org.eclipse.jetty.io.ByteBufferPool.Lease;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HpackEncoder public class HpackEncoder
{ {
public static final Logger LOG = Log.getLogger(HpackEncoder.class);
private final static HttpField[] __status= new HttpField[599]; private final static HttpField[] __status= new HttpField[599];
private final static EnumSet<HttpHeader> __NEVER_INDEX = private final static EnumSet<HttpHeader> __NEVER_INDEX =
@ -100,7 +104,6 @@ public class HpackEncoder
public void encodeMaxHeaderTableSize(ByteBuffer buffer, int maxHeaderTableSize) public void encodeMaxHeaderTableSize(ByteBuffer buffer, int maxHeaderTableSize)
{ {
// TODO
_context.resize(maxHeaderTableSize); _context.resize(maxHeaderTableSize);
} }
@ -143,34 +146,29 @@ public class HpackEncoder
_context.removedUnusedReferences(buffer); _context.removedUnusedReferences(buffer);
} }
private void encode(ByteBuffer buffer, HttpField field) private void encode(ByteBuffer buffer, HttpField field)
{ {
final int p=LOG.isDebugEnabled()?buffer.position():-1;
/* String encoding=null;
System.err.println("encode "+field);
int p=buffer.position();
try{
*/
// TODO currently we do not check if there is enough space, so we will always // TODO currently we do not check if there is enough space, so we will always
// return true or fail nastily. // return true or fail nastily.
// Is there an entry for the field? // Is there an entry for the field?
Entry entry = _context.get(field); Entry entry = _context.get(field);
if (entry!=null) if (entry!=null)
{ {
// if entry is already in the reference set, then nothing more to do. // if entry is already in the reference set, then nothing more to do.
if (entry.isInReferenceSet()) if (entry.isInReferenceSet())
{ {
entry.used(); entry.used();
// System.err.println("In Reference Set"); encoding="InRefSet";
return;
} }
// Is this as static field // Is this as static field
if (entry.isStatic()) else if (entry.isStatic())
{ {
// TODO Strategy decision to make! // TODO Strategy decision to make!
// Should we add to reference set or just always send as indexed? // Should we add to reference set or just always send as indexed?
@ -179,130 +177,130 @@ public class HpackEncoder
// lets send as literal header, indexed name. // lets send as literal header, indexed name.
// We don't need never indexed because the cookie fields are name only and we can // We don't need never indexed because the cookie fields are name only and we can
// huffman encode the value for the same reason. // huffman encode the value for the same reason.
// Add the token // Add the token
buffer.put((byte)0x00); buffer.put((byte)0x00);
// Add the name index // Add the name index
NBitInteger.encode(buffer,4,_context.index(entry)); NBitInteger.encode(buffer,4,_context.index(entry));
// Add the value // Add the value
buffer.put(entry.getStaticHuffmanValue()); buffer.put(entry.getStaticHuffmanValue());
// System.err.println("Literal without Indexing, indexed name"); encoding="LiteralStaticIdxNameHuffmanValue";
return;
} }
else
// System.err.println("Indexed from Header Set");
// 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());
// System.err.printf("Literal huff=%b refed=%b neverIdx=%b nameIdx=%b b=%d m=0x%02x%n",huffman,reference,never_index,name_entry!=null,name_bits,mask);
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)0x00);
NBitInteger.encode(buffer,7,value.length());
for (int i=0;i<value.length();i++)
{ {
char c=value.charAt(i); encoding="IdxField";
if (c<' '|| c>127)
throw new IllegalArgumentException(); // So we can add the entry to the reference Set and emit the index;
buffer.put((byte)c); _context.addToRefSet(entry);
int index=_context.index(entry);
// TODO pregenerate indexes?
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index);
} }
} }
else
// If we want the field referenced, then we add it to our
// table and reference set.
if (reference)
{ {
Entry new_entry=_context.add(field); // Must be a new entry, so we will have to send literally.
if (new_entry!=null) // TODO Strategy decision to make!
_context.addToRefSet(new_entry); // 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 (p>=0)
{
encoding="Literal"+
((name_entry==null)?"IdxName":"HuffName")+
(huffman?"HuffName":"LitName")+
(reference?"Idx":(never_index?"NeverIdx":""));
}
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)0x00);
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; if (p>=0)
/*
}
finally
{ {
int e=buffer.position(); int e=buffer.position();
System.err.println("'"+TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p)+"'"); LOG.debug("encoded '{}' by {} to '{}'",field,encoding,TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+p,e-p));
} }
*/
} }
} }

View File

@ -30,12 +30,16 @@ public class StaticValueHttpField extends HttpField
public StaticValueHttpField(HttpHeader header,String name, String valueString, Object value) public StaticValueHttpField(HttpHeader header,String name, String valueString, Object value)
{ {
super(header,name,valueString); super(header,name,valueString);
if (value==null)
throw new IllegalArgumentException();
_value=value; _value=value;
} }
public StaticValueHttpField(String name, String valueString, Object value) public StaticValueHttpField(String name, String valueString, Object value)
{ {
super(name,valueString); super(name,valueString);
if (value==null)
throw new IllegalArgumentException();
_value=value; _value=value;
} }

View File

@ -25,6 +25,7 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -35,6 +36,9 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.generator.Generator; import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.hpack.HpackContext;
import org.eclipse.jetty.http2.hpack.HpackDecoder;
import org.eclipse.jetty.http2.hpack.HpackEncoder;
import org.eclipse.jetty.http2.hpack.MetaData; import org.eclipse.jetty.http2.hpack.MetaData;
import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
@ -71,6 +75,9 @@ public class HTTP2ServerTest
generator = new Generator(byteBufferPool); generator = new Generator(byteBufferPool);
server.start(); server.start();
HpackContext.LOG.setDebugEnabled(true);
HpackEncoder.LOG.setDebugEnabled(true);
HpackDecoder.LOG.setDebugEnabled(true);
} }
@After @After