Updates to hpack for hpack-09 draft

This commit is contained in:
Greg Wilkins 2014-07-31 13:26:23 +10:00
parent a14b2be0e6
commit 9c3eedfea8
9 changed files with 200 additions and 184 deletions

View File

@ -20,6 +20,8 @@ package org.eclipse.jetty.http;
import java.util.ArrayList;
import org.eclipse.jetty.util.StringUtil;
/* ------------------------------------------------------------ */
/** A HTTP Field
*/
@ -67,6 +69,11 @@ public class HttpField
return _value;
}
public long getLongValue()
{
return StringUtil.toLong(_value);
}
public String[] getValues()
{
ArrayList<String> list = new ArrayList<>();
@ -349,19 +356,6 @@ public class HttpField
}
@Override
public String toString()
{
@ -420,4 +414,28 @@ public class HttpField
return false;
return true;
}
public static class LongValueHttpField extends HttpField
{
final long _long;
public LongValueHttpField(HttpHeader header, long value)
{
super(header,Long.toString(value));
_long=value;
}
public LongValueHttpField(HttpHeader header, String value)
{
super(header,value);
_long=StringUtil.toLong(value);
}
@Override
public long getLongValue()
{
return _long;
}
}
}

View File

@ -474,7 +474,7 @@ public class HttpFields implements Iterable<HttpField>
public long getLongField(String name) throws NumberFormatException
{
HttpField field = getField(name);
return field==null?-1L:StringUtil.toLong(field.getValue());
return field==null?-1L:field.getLongValue();
}
/**

View File

@ -51,7 +51,7 @@ public class HeadersGenerator extends FrameGenerator
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
encoder.encode(metaData, lease);
encoder.encode(metaData, lease,Frame.MAX_LENGTH);
long length = lease.getTotalLength();
if (length > Frame.MAX_LENGTH)

View File

@ -53,7 +53,7 @@ public class PushPromiseGenerator extends FrameGenerator
if (promisedStreamId < 0)
throw new IllegalArgumentException("Invalid promised stream id: " + promisedStreamId);
encoder.encode(metaData, lease);
encoder.encode(metaData, lease, Frame.MAX_LENGTH);
// The promised streamId.
int fixedLength = 4;

View File

@ -117,20 +117,20 @@ public class HpackContext
switch(i)
{
case 2:
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.GET),true);
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.GET));
break;
case 3:
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.POST),false);
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpMethod.POST));
break;
case 6:
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTP),true);
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTP));
break;
case 7:
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTPS),false);
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],HttpScheme.HTTPS));
break;
case 8:
case 11:
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])),true);
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])));
break;
case 9:
@ -138,11 +138,11 @@ public class HpackContext
case 12:
case 13:
case 14:
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])),false);
entry=new StaticEntry(i,new StaticValueHttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1],Integer.valueOf(STATIC_TABLE[i][1])));
break;
default:
entry=new StaticEntry(i,new HttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1]),true);
entry=new StaticEntry(i,new HttpField(STATIC_TABLE[i][0],STATIC_TABLE[i][1]));
}
__staticTable[i]=entry;
@ -412,9 +412,8 @@ public class HpackContext
public static class StaticEntry extends Entry
{
private final byte[] _huffmanValue;
private final boolean _useRefSet;
StaticEntry(int index,HttpField field,boolean useRefSet)
StaticEntry(int index,HttpField field)
{
super(index,field);
String value = field.getValue();
@ -434,7 +433,6 @@ public class HpackContext
}
else
_huffmanValue=null;
_useRefSet=useRefSet;
}
@Override
@ -442,11 +440,6 @@ public class HpackContext
{
return true;
}
public boolean useRefSet()
{
return _useRefSet;
}
@Override
public byte[] getStaticHuffmanValue()

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
@ -41,6 +42,7 @@ import org.eclipse.jetty.util.log.Logger;
public class HpackDecoder
{
public static final Logger LOG = Log.getLogger(HpackDecoder.class);
public final static HttpField.LongValueHttpField CONTENT_LENGTH_0 = new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,0L);
private final HpackContext _context;
private final MetaDataBuilder _builder;
@ -79,7 +81,7 @@ public class HpackDecoder
byte b = buffer.get();
if (b<0)
{
// indexed
// 7.1 indexed if the high bit is set
int index = NBitInteger.decode(buffer,7);
Entry entry=_context.get(index);
if (entry==null)
@ -107,126 +109,143 @@ public class HpackDecoder
else
{
// look at the first nibble in detail
int f=(b&0xF0)>>4;
byte f= (byte)((b&0xF0)>>4);
String name;
HttpHeader header;
String value;
boolean indexed;
int name_index;
if (f<=1 || f>=4)
switch (f)
{
// literal
boolean indexed=f>=4;
int bits=indexed?6:4;
boolean huffmanName=false;
case 2: // 7.3
case 3: // 7.3
// change table size
int size = NBitInteger.decode(buffer,5);
if (LOG.isDebugEnabled())
LOG.debug("decode resize="+size);
if (size>_localMaxHeaderTableSize)
throw new IllegalArgumentException();
_context.resize(size);
continue;
// decode the name
int name_index=NBitInteger.decode(buffer,bits);
if (name_index>0)
{
Entry name_entry=_context.get(name_index);
name=name_entry.getHttpField().getName();
header=name_entry.getHttpField().getHeader();
}
else
{
huffmanName = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
_builder.checkSize(length,huffmanName);
if (huffmanName)
name=Huffman.decode(buffer,length);
else
name=toASCIIString(buffer,length);
for (int i=0;i<name.length();i++)
{
char c=name.charAt(i);
if (c>='A'&&c<='Z')
{
throw new BadMessageException(400,"Uppercase header name");
}
}
header=HttpHeader.CACHE.get(name);
}
// decode the value
boolean huffmanValue = (buffer.get()&0x80)==0x80;
case 0: // 7.2.2
case 1: // 7.2.3
indexed=false;
name_index=NBitInteger.decode(buffer,4);
break;
case 4: // 7.2.1
case 5: // 7.2.1
case 6: // 7.2.1
case 7: // 7.2.1
indexed=true;
name_index=NBitInteger.decode(buffer,6);
break;
default:
throw new IllegalStateException();
}
boolean huffmanName=false;
// decode the name
if (name_index>0)
{
Entry name_entry=_context.get(name_index);
name=name_entry.getHttpField().getName();
header=name_entry.getHttpField().getHeader();
}
else
{
huffmanName = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
_builder.checkSize(length,huffmanValue);
if (huffmanValue)
value=Huffman.decode(buffer,length);
_builder.checkSize(length,huffmanName);
if (huffmanName)
name=Huffman.decode(buffer,length);
else
value=toASCIIString(buffer,length);
// Make the new field
HttpField field;
switch(name)
name=toASCIIString(buffer,length);
for (int i=0;i<name.length();i++)
{
case ":method":
HttpMethod method=HttpMethod.CACHE.get(value);
if (method!=null)
field = new StaticValueHttpField(header,name,method.asString(),method);
else
field = new AuthorityHttpField(value);
break;
case ":status":
Integer code = Integer.valueOf(value);
field = new StaticValueHttpField(header,name,value,code);
break;
case ":scheme":
HttpScheme scheme=HttpScheme.CACHE.get(value);
if (scheme!=null)
field = new StaticValueHttpField(header,name,scheme.asString(),scheme);
else
field = new AuthorityHttpField(value);
break;
case ":authority":
char c=name.charAt(i);
if (c>='A'&&c<='Z')
{
throw new BadMessageException(400,"Uppercase header name");
}
}
header=HttpHeader.CACHE.get(name);
}
// decode the value
boolean huffmanValue = (buffer.get()&0x80)==0x80;
int length = NBitInteger.decode(buffer,7);
_builder.checkSize(length,huffmanValue);
if (huffmanValue)
value=Huffman.decode(buffer,length);
else
value=toASCIIString(buffer,length);
// Make the new field
HttpField field;
switch(name)
{
case ":method":
HttpMethod method=HttpMethod.CACHE.get(value);
if (method!=null)
field = new StaticValueHttpField(header,name,method.asString(),method);
else
field = new AuthorityHttpField(value);
break;
case ":status":
Integer code = Integer.valueOf(value);
field = new StaticValueHttpField(header,name,value,code);
break;
case ":scheme":
HttpScheme scheme=HttpScheme.CACHE.get(value);
if (scheme!=null)
field = new StaticValueHttpField(header,name,scheme.asString(),scheme);
else
field = new AuthorityHttpField(value);
break;
case ":path":
// TODO is this needed
/*
if (indexed)
field = new StaticValueHttpField(header,name,value,new HttpURI(value));
else*/
field = new HttpField(header,name,value);
break;
default:
field = new HttpField(header,name,value);
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
_builder.emit(field);
// if indexed
if (indexed)
{
// add to header table
_context.add(field);
}
break;
case ":authority":
field = new AuthorityHttpField(value);
break;
case ":path":
field = new HttpField(header,name,value);
break;
case "content-length":
if ("0".equals(value))
field = CONTENT_LENGTH_0;
else
field = new HttpField.LongValueHttpField(header,value);
break;
default:
field = new HttpField(header,name,value);
break;
}
else if (f==2)
if (LOG.isDebugEnabled())
LOG.debug("decoded '"+field+"' by Lit"+(name_index>0?"IdxName":(huffmanName?"HuffName":"LitName"))+(huffmanValue?"HuffVal":"LitVal")+(indexed?"Idx":""));
// emit the field
_builder.emit(field);
// if indexed
if (indexed)
{
// change table size
int size = NBitInteger.decode(buffer,4);
if (LOG.isDebugEnabled())
LOG.debug("decode resize="+size);
if (size>_localMaxHeaderTableSize)
throw new IllegalArgumentException();
_context.resize(size);
// add to header table
_context.add(field);
}
else if (f==3)
{
if (LOG.isDebugEnabled())
LOG.debug("unused");
}
}
}

View File

@ -42,9 +42,7 @@ public class HpackEncoder
private final static EnumSet<HttpHeader> __DO_NOT_HUFFMAN =
EnumSet.of(HttpHeader.COOKIE,
HttpHeader.SET_COOKIE,
HttpHeader.SET_COOKIE2,
EnumSet.of(
HttpHeader.AUTHORIZATION,
HttpHeader.CONTENT_MD5,
HttpHeader.PROXY_AUTHENTICATE,
@ -113,11 +111,11 @@ public class HpackEncoder
_localMaxHeaderTableSize=localMaxHeaderTableSize;
}
public void encode(MetaData metadata,Lease lease)
// TODO better handling of buffer size
public void encode(MetaData metadata,Lease lease,int buffersize)
{
ByteBuffer buffer = lease.acquire(8*1024,false); // TODO make size configurable
ByteBuffer buffer = lease.acquire(buffersize,false);
lease.append(buffer,true);
// TODO handle multiple buffers if large size configured.
BufferUtil.clearToFill(buffer);
encode(buffer,metadata);
BufferUtil.flipToFlush(buffer,0);
@ -170,7 +168,7 @@ public class HpackEncoder
if (maxHeaderTableSize>_remoteMaxHeaderTableSize)
throw new IllegalArgumentException();
buffer.put((byte)0x20);
NBitInteger.encode(buffer,4,maxHeaderTableSize);
NBitInteger.encode(buffer,5,maxHeaderTableSize);
_context.resize(maxHeaderTableSize);
}
@ -188,87 +186,67 @@ public class HpackEncoder
if (entry!=null)
{
// Is this as static field
if (entry.isStatic())
{
// entries like :status: 200 and :method: GET are worthwhile putting into ref set.
// as they are likely to be repeated.
encoding="StaticIndexed";
int index=_context.index(entry);
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index);
// TODO Copy?
// entry=_context.add(entry.getHttpField());
}
else
{
// So we can emit the index and add the entry to the reference Set
int index=_context.index(entry);
if (p>=0)
encoding="IdxField"+(1+NBitInteger.octectsNeeded(7,index));
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index);
}
int index=_context.index(entry);
if (p>=0)
encoding="Indexed"+index;
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index);
}
else
{
// 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 indexed;
final int name_bits;
final byte mask;
if (header==null)
{
// unknown header.
never_index=false;
huffman=true;
indexed=true;
name_bits = 6;
mask=(byte)0x40;
buffer.put((byte)0x40);
}
else if (__DO_NOT_INDEX.contains(header))
{
// Non indexed field
indexed=false;
never_index=__NEVER_INDEX.contains(header);
huffman=!__DO_NOT_HUFFMAN.contains(header);
name_bits = 4;
mask=never_index?(byte)0x01:(byte)0x00;
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;
mask=(byte)0x00;
buffer.put((byte)0x00);
}
else
{
// indexed
indexed=true;
never_index=false;
huffman=!__DO_NOT_HUFFMAN.contains(header);
name_bits = 6;
mask=(byte)0x40;
buffer.put((byte)0x40);
}
// Add the mask bits
buffer.put(mask);
// Look for a name Index
Entry name_entry = _context.get(field.getName());
if (p>=0)
{
encoding="Lit"+
((name_entry==null)?"HuffName":("IdxName"+(_context.index(name_entry)<64?'1':'X')))+
(huffman?"HuffVal":"LitVal")+
(indexed?"Idxd":(never_index?"NeverIdx":""));
((name_entry==null)?"HuffN":"IdxN")+
(huffman?"HuffV":"LitV")+
(indexed?"Idx":(never_index?"!!Idx":"!Idx"));
}
if (name_entry!=null)

View File

@ -114,9 +114,9 @@ public class MetaDataBuilder
break;
case ":path":
_path=field.getValue();
_path = field.getValue();
break;
default:
if (field.getName().charAt(0)!=':')
_fields.add(field);

View File

@ -35,6 +35,14 @@ public class StaticValueHttpField extends HttpField
_value=value;
}
public StaticValueHttpField(HttpHeader header,String valueString, Object value)
{
super(header,header.asString(),valueString);
if (value==null)
throw new IllegalArgumentException();
_value=value;
}
public StaticValueHttpField(String name, String valueString, Object value)
{
super(name,valueString);