improve known header handling in hpack encoding

This commit is contained in:
Greg Wilkins 2014-08-02 15:40:15 +10:00
parent aaa2e5c6c1
commit cd59d0085e
4 changed files with 87 additions and 55 deletions

View File

@ -111,16 +111,26 @@ public enum HttpHeader
X_POWERED_BY("X-Powered-By"),
/* ------------------------------------------------------------ */
/** HTTP2 Fields.
*/
C_METHOD(":method"),
C_SCHEME(":scheme"),
C_AUTHORITY(":authority"),
C_PATH(":path"),
C_STATUS(":status"),
UNKNOWN("::UNKNOWN::");
/* ------------------------------------------------------------ */
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(512);
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(520);
static
{
for (HttpHeader header : HttpHeader.values())
if (header!=UNKNOWN)
CACHE.put(header.toString(),header);
if (!CACHE.put(header.toString(),header))
throw new IllegalStateException();
}
private final String _string;

View File

@ -25,8 +25,10 @@ import java.util.Map;
import java.util.Set;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.StringUtil;
@ -106,7 +108,7 @@ public class HpackContext
private static final Map<HttpField,Entry> __staticFieldMap = new HashMap<>();
private static final Trie<Entry> __staticNameMap = new ArrayTernaryTrie<>(true,512);
private static final Entry[] __headerEntryTable = new Entry[HttpHeader.UNKNOWN.ordinal()];
private static final StaticEntry[] __staticTable=new StaticEntry[STATIC_TABLE.length];
static
{
@ -158,6 +160,13 @@ public class HpackContext
throw new IllegalStateException("name trie too small");
}
}
for (HttpHeader h : HttpHeader.values())
{
Entry entry = __staticNameMap.get(h.asString());
if (entry!=null)
__headerEntryTable[h.ordinal()]=entry;
}
}
@ -215,6 +224,14 @@ public class HpackContext
return null;
}
public Entry get(HttpHeader header)
{
Entry e = __headerEntryTable[header.ordinal()];
if (e==null)
return get(header.asString());
return e;
}
public Entry add(HttpField field)
{
int slot=_headerTable.getNextSlotUnsafe();

View File

@ -50,6 +50,7 @@ public class HpackEncoder
private final static EnumSet<HttpHeader> __DO_NOT_INDEX =
EnumSet.of(
// TODO ??? HttpHeader.C_PATH,
HttpHeader.CONTENT_MD5,
HttpHeader.CONTENT_RANGE,
HttpHeader.ETAG,
@ -139,10 +140,10 @@ public class HpackEncoder
// TODO optimise these to avoid HttpField creation
String scheme=request.getURI().getScheme();
encode(buffer,new HttpField(":scheme",scheme==null?HttpScheme.HTTP.asString():scheme));
encode(buffer,new HttpField(":method",request.getMethod()));
encode(buffer,new HttpField(":authority",request.getURI().getAuthority()));
encode(buffer,new HttpField(":path",request.getURI().getPathQuery()));
encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
}
else if (metadata.isResponse())
@ -151,7 +152,7 @@ public class HpackEncoder
int code=response.getStatus();
HttpField status = code<__status.length?__status[code]:null;
if (status==null)
status=new HttpField(":status",Integer.toString(code));
status=new HttpField(HttpHeader.C_STATUS,Integer.toString(code));
encode(buffer,status);
}
@ -188,7 +189,7 @@ public class HpackEncoder
{
int index=_context.index(entry);
if (p>=0)
encoding="Indexed"+index;
encoding="IdxField"+(entry.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(7,index));
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index);
}
@ -196,18 +197,18 @@ public class HpackEncoder
{
// Must be a new entry, so we will have to send literally.
// Look for a name Index
Entry name_entry = _context.get(field.getName());
HttpHeader header = field.getHeader();
final Entry name;
final boolean indexed;
final boolean never_index;
final boolean huffman;
final boolean indexed;
final int name_bits;
final int bits;
HttpHeader header = field.getHeader();
if (header==null)
{
name = _context.get(field.getName());
// has the custom header name been seen before?
if (name_entry==null)
if (name==null)
{
// unknown name and value, so let's index this just in case it is
// the first time we have seen a custom name or a custom field.
@ -215,7 +216,7 @@ public class HpackEncoder
indexed=true;
never_index=false;
huffman=true;
name_bits = 6;
bits = 6;
buffer.put((byte)0x40);
}
else
@ -225,49 +226,53 @@ public class HpackEncoder
indexed=false;
never_index=false;
huffman=true;
name_bits = 4;
bits = 4;
buffer.put((byte)0x00);
}
}
else if (__DO_NOT_INDEX.contains(header))
else
{
// Non indexed field
indexed=false;
never_index=__NEVER_INDEX.contains(header);
huffman=!__DO_NOT_HUFFMAN.contains(header);
name_bits = 4;
buffer.put(never_index?(byte)0x10:(byte)0x00);
}
else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
{
// Non indexed content length for non zero value
indexed=false;
never_index=false;
huffman=true;
name_bits = 4;
buffer.put((byte)0x00);
}
else
{
// indexed
indexed=true;
never_index=false;
huffman=!__DO_NOT_HUFFMAN.contains(header);
name_bits = 6;
buffer.put((byte)0x40);
}
name = _context.get(header);
if (__DO_NOT_INDEX.contains(header))
{
// Non indexed field
indexed=false;
never_index=__NEVER_INDEX.contains(header);
huffman=!__DO_NOT_HUFFMAN.contains(header);
bits = 4;
buffer.put(never_index?(byte)0x10:(byte)0x00);
}
else if (header==HttpHeader.CONTENT_LENGTH && field.getValue().length()>1)
{
// Non indexed content length for non zero value
indexed=false;
never_index=false;
huffman=true;
bits = 4;
buffer.put((byte)0x00);
}
else
{
// indexed
indexed=true;
never_index=false;
huffman=!__DO_NOT_HUFFMAN.contains(header);
bits = 6;
buffer.put((byte)0x40);
}
}
if (p>=0)
{
encoding="Lit"+
((name_entry==null)?"HuffN":"IdxN")+
((name==null)?"HuffN":("IdxN"+(name.isStatic()?"S":"")+(1+NBitInteger.octectsNeeded(bits,_context.index(name)))))+
(huffman?"HuffV":"LitV")+
(indexed?"Idx":(never_index?"!!Idx":"!Idx"));
}
if (name_entry!=null)
NBitInteger.encode(buffer,name_bits,_context.index(name_entry));
if (name!=null)
NBitInteger.encode(buffer,bits,_context.index(name));
else
{
// leave name index bits as 0

View File

@ -52,17 +52,17 @@ public class HpackPerfTest
@After
public void after()
{
System.err.printf("headertable=%d unencoded=%d encoded=%d p=%d%%%n",_maxHeaderTableSize,_unencodedSize,_encodedSize,(100*_encodedSize+49)/_unencodedSize);
System.err.printf("headertable=%d unencoded=%d encoded=%d p=%3.1f%%%n",_maxHeaderTableSize,_unencodedSize,_encodedSize,100.0*_encodedSize/_unencodedSize);
}
@Test
public void simpleTest() throws Exception
{
runStories(_maxHeaderTableSize,new HpackEncoder(_maxHeaderTableSize,_maxHeaderTableSize));
runStories(_maxHeaderTableSize);
}
private void runStories(int maxHeaderTableSize, HpackEncoder encoder) throws Exception
private void runStories(int maxHeaderTableSize) throws Exception
{
// Find files
File data = MavenTestingUtils.getTestResourceDir("data");
@ -84,25 +84,25 @@ public class HpackPerfTest
ByteBuffer buffer = BufferUtil.allocate(256*1024);
// Encode all the requests
encodeStories(buffer,stories,"request",encoder);
encodeStories(buffer,stories,"request");
// clear table
BufferUtil.clearToFill(buffer);
encoder.encodeMaxHeaderTableSize(buffer,0);
encoder.encodeMaxHeaderTableSize(buffer,maxHeaderTableSize);
BufferUtil.flipToFlush(buffer,0);
// Encode all the responses
encodeStories(buffer,stories,"response",encoder);
encodeStories(buffer,stories,"response");
}
private void encodeStories(ByteBuffer buffer,Map<String,Object>[] stories, String type, HpackEncoder encoder) throws Exception
private void encodeStories(ByteBuffer buffer,Map<String,Object>[] stories, String type) throws Exception
{
for (Map<String,Object> story : stories)
{
if (type.equals(story.get("context")))
{
HpackEncoder encoder = new HpackEncoder(_maxHeaderTableSize,_maxHeaderTableSize);
// System.err.println(story);
Object[] cases = (Object[])story.get("cases");
for (Object c : cases)