encoder headers as lowercase

This commit is contained in:
Greg Wilkins 2014-06-18 10:22:23 +02:00
parent e115dee62f
commit 140e7ed0c5
10 changed files with 70 additions and 20 deletions

View File

@ -319,7 +319,7 @@ public class HpackContext
else
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("RefSet[%x] remove unused",hashCode(),entry));
LOG.debug(String.format("RefSet[%x] remove unused %s",hashCode(),entry));
// encode the reference to remove it
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,index(entry));
@ -339,7 +339,7 @@ public class HpackContext
else
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("RefSet[%x] emit unref",hashCode(),entry));
LOG.debug(String.format("RefSet[%x] emit unref %s",hashCode(),entry));
builder.emit(entry.getHttpField());
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
@ -132,6 +133,14 @@ public class HpackDecoder
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);
}

View File

@ -302,10 +302,10 @@ public class HpackEncoder
NBitInteger.encode(buffer,name_bits,_context.index(name_entry));
else
{
// Encode the name always with huffman
// Encode the name always with lowercase huffman
buffer.put((byte)0x80);
NBitInteger.encode(buffer,7,Huffman.octetsNeeded(field.getName()));
Huffman.encode(buffer,field.getName());
NBitInteger.encode(buffer,7,Huffman.octetsNeededLC(field.getName()));
Huffman.encodeLC(buffer,field.getName());
}
// Add the literal value
@ -339,8 +339,6 @@ public class HpackEncoder
if (new_entry!=null)
_context.addToRefSet(new_entry);
}
}
if (p>=0)

View File

@ -287,15 +287,21 @@ public class Huffman
};
static final int[][] LCCODES = new int[CODES.length][];
// Huffman decode tree stored in a flattened char array for good
// locality of reference.
static final char[] tree;
static final char[] rowsym;
static final byte[] rowbits;
// Build the Huffman lookup tree
// Build the Huffman lookup tree and LC TABLE
static
{
System.arraycopy(CODES,0,LCCODES,0,CODES.length);
for (int i='A';i<='Z';i++)
LCCODES[i]=LCCODES['a'+i-'A'];
int r=0;
for (int i=0;i<CODES.length;i++)
r+=(CODES[i][1]+7)/8;
@ -339,12 +345,12 @@ public class Huffman
}
}
static public String decode(ByteBuffer buffer)
public static String decode(ByteBuffer buffer)
{
return decode(buffer,buffer.remaining());
}
static public String decode(ByteBuffer buffer,int length)
public static String decode(ByteBuffer buffer,int length)
{
StringBuilder out = new StringBuilder(length*2);
int node = 0;
@ -398,7 +404,27 @@ public class Huffman
return out.toString();
}
static public int octetsNeeded(String s)
public static int octetsNeeded(String s)
{
return octetsNeeded(CODES,s);
}
public static void encode(ByteBuffer buffer,String s)
{
encode(CODES,buffer,s);
}
public static int octetsNeededLC(String s)
{
return octetsNeeded(LCCODES,s);
}
public static void encodeLC(ByteBuffer buffer, String s)
{
encode(LCCODES,buffer,s);
}
private static int octetsNeeded(final int[][] table,String s)
{
int needed=0;
int len = s.length();
@ -407,13 +433,13 @@ public class Huffman
char c=s.charAt(i);
if (c>=128 || c<' ')
throw new IllegalArgumentException();
needed += CODES[c][1];
needed += table[c][1];
}
return (needed+7) / 8;
}
static public void encode(ByteBuffer buffer,String s)
private static void encode(final int[][] table,ByteBuffer buffer,String s)
{
long current = 0;
int n = 0;
@ -427,8 +453,8 @@ public class Huffman
char c=s.charAt(i);
if (c>=128 || c<' ')
throw new IllegalArgumentException();
int code = CODES[c][0];
int bits = CODES[c][1];
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
@ -450,4 +476,5 @@ public class Huffman
buffer.position(p-buffer.arrayOffset());
}
}

View File

@ -41,7 +41,7 @@ public class MetaDataBuilder
HttpFields _fields = new HttpFields(10);
public void emit(HttpField field)
{
{
if (field instanceof StaticValueHttpField)
{
StaticValueHttpField value = (StaticValueHttpField)field;

View File

@ -67,7 +67,7 @@ public class HpackTest
fields1.add(HttpHeader.CONTENT_TYPE,"text/plain");
fields1.add(HttpHeader.CONTENT_LENGTH,"1234");
fields1.add(HttpHeader.SERVER,"jetty");
fields1.add("custom-key","other-value");
fields1.add("Custom-Key","Other-Value");
Response original1 = new Response(HttpVersion.HTTP_2,200,fields1);
// Same again?
@ -77,7 +77,7 @@ public class HpackTest
Response decoded1 = (Response)decoder.decode(buffer);
Assert.assertEquals(original1,decoded1);
Assert.assertEquals("custom-key",decoded1.getFields().getField("Custom-Key").getName());
}
}

View File

@ -43,6 +43,8 @@ public class HttpChannelOverHTTP2 extends HttpChannel
{
private static final Logger LOG = Log.getLogger(HttpChannelOverHTTP2.class);
private static final HttpField ACCEPT_ENCODING_GZIP = new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip");
private static final HttpField SERVER_VERSION=new HttpField(HttpHeader.SERVER,HttpConfiguration.SERVER_VERSION);
private static final HttpField POWERED_BY=new HttpField(HttpHeader.X_POWERED_BY,HttpConfiguration.SERVER_VERSION);
private final Stream stream;
public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input, Stream stream)
@ -69,6 +71,13 @@ public class HttpChannelOverHTTP2 extends HttpChannel
if (!fields.contains(HttpHeader.ACCEPT_ENCODING, "gzip"))
fields.add(ACCEPT_ENCODING_GZIP);
// TODO make this a better field for h2 hpack generation
if (getHttpConfiguration().getSendServerVersion())
getResponse().getHttpFields().add(SERVER_VERSION);
if (getHttpConfiguration().getSendXPoweredBy())
getResponse().getHttpFields().add(POWERED_BY);
onRequest(request);
if (frame.isEndStream())

View File

@ -21,8 +21,10 @@ package org.eclipse.jetty.http2.server;
import java.io.IOException;
import java.util.Date;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -116,7 +118,10 @@ public class Http2Server
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
HttpSession session = request.getSession(true);
response.setHeader("custom","value");
if (session.isNew())
response.addCookie(new Cookie("bigcookie",
"This is a test cookies that was created on "+new Date()+" and is used by the jetty http/2 test servlet."));
response.setHeader("Custom","Value");
response.setContentType("text/plain");
String content = "Hello from Jetty using "+request.getProtocol() +"\n";
content+="uri="+request.getRequestURI()+"\n";

View File

@ -1,2 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.http2.LEVEL=INFO
org.eclipse.jetty.http2.hpack.LEVEL=DEBUG

View File

@ -30,7 +30,7 @@ public class Jetty
pkg.getImplementationVersion() != null)
VERSION = pkg.getImplementationVersion();
else
VERSION = System.getProperty("jetty.version", "9.2.z-SNAPSHOT");
VERSION = System.getProperty("jetty.version", "9.3.z-SNAPSHOT");
}
private Jetty()