Skip to content

Commit

Permalink
Merge pull request #5566 from lorban/tries-cleanup
Browse files Browse the repository at this point in the history
Tries cleanup
  • Loading branch information
lorban committed Nov 26, 2020
2 parents df5622e + aa8bd5d commit 981e263
Show file tree
Hide file tree
Showing 30 changed files with 1,363 additions and 1,045 deletions.
17 changes: 7 additions & 10 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@
import java.util.stream.Stream;

import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -92,13 +91,11 @@ public enum Result
private final int _send;
private static final int SEND_SERVER = 0x01;
private static final int SEND_XPOWEREDBY = 0x02;
private static final Trie<Boolean> ASSUMED_CONTENT_METHODS = new ArrayTrie<>(8);

static
{
ASSUMED_CONTENT_METHODS.put(HttpMethod.POST.asString(), Boolean.TRUE);
ASSUMED_CONTENT_METHODS.put(HttpMethod.PUT.asString(), Boolean.TRUE);
}
private static final Index<Boolean> ASSUMED_CONTENT_METHODS = new Index.Builder<Boolean>()
.caseSensitive(false)
.with(HttpMethod.POST.asString(), Boolean.TRUE)
.with(HttpMethod.PUT.asString(), Boolean.TRUE)
.build();

public static void setJettyVersion(String serverVersion)
{
Expand Down Expand Up @@ -679,7 +676,7 @@ else if (contentLength != field.getLongValue())
// Calculate how to end _content and connection, _content length and transfer encoding
// settings from http://tools.ietf.org/html/rfc7230#section-3.3.3

boolean assumedContentRequest = request != null && Boolean.TRUE.equals(ASSUMED_CONTENT_METHODS.get(request.getMethod()));
boolean assumedContentRequest = request != null && ASSUMED_CONTENT_METHODS.get(request.getMethod()) != null;
boolean assumedContent = assumedContentRequest || contentType || chunkedHint;
boolean noContentRequest = request != null && contentLength <= 0 && !assumedContent;

Expand Down
22 changes: 6 additions & 16 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@

import java.nio.ByteBuffer;

import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;

public enum HttpHeader
{
Expand Down Expand Up @@ -133,21 +132,12 @@ public enum HttpHeader
C_AUTHORITY(":authority", true),
C_PATH(":path", true),
C_STATUS(":status", true),
C_PROTOCOL(":protocol"),
C_PROTOCOL(":protocol");

UNKNOWN("::UNKNOWN::", true);

public static final Trie<HttpHeader> CACHE = new ArrayTrie<>(630);

static
{
for (HttpHeader header : HttpHeader.values())
{
if (header != UNKNOWN)
if (!CACHE.put(header.toString(), header))
throw new IllegalStateException();
}
}
public static final Index<HttpHeader> CACHE = new Index.Builder<HttpHeader>()
.caseSensitive(false)
.withAll(HttpHeader.values(), HttpHeader::toString)
.build();

private final String _string;
private final String _lowerCase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
import java.nio.ByteBuffer;
import java.util.EnumSet;

import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.Index;

/**
*
Expand All @@ -40,19 +39,12 @@ public enum HttpHeaderValue
TE("TE"),
BYTES("bytes"),
NO_CACHE("no-cache"),
UPGRADE("Upgrade"),
UNKNOWN("::UNKNOWN::");
UPGRADE("Upgrade");

public static final Trie<HttpHeaderValue> CACHE = new ArrayTrie<HttpHeaderValue>();

static
{
for (HttpHeaderValue value : HttpHeaderValue.values())
{
if (value != UNKNOWN)
CACHE.put(value.toString(), value);
}
}
public static final Index<HttpHeaderValue> CACHE = new Index.Builder<HttpHeaderValue>()
.caseSensitive(false)
.withAll(HttpHeaderValue.values(), HttpHeaderValue::toString)
.build();

private final String _string;
private final ByteBuffer _buffer;
Expand Down
33 changes: 13 additions & 20 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@

import java.nio.ByteBuffer;

import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;

/**
* Known HTTP Methods
Expand Down Expand Up @@ -142,29 +140,24 @@ public String toString()
return _method;
}

public static final Trie<HttpMethod> INSENSITIVE_CACHE = new ArrayTrie<>(252);
public static final Trie<HttpMethod> CACHE = new ArrayTernaryTrie<>(false, 300);
public static final Trie<HttpMethod> LOOK_AHEAD = new ArrayTernaryTrie<>(false, 330);
public static final Index<HttpMethod> INSENSITIVE_CACHE = new Index.Builder<HttpMethod>()
.caseSensitive(false)
.withAll(HttpMethod.values(), HttpMethod::asString)
.build();
public static final Index<HttpMethod> CACHE = new Index.Builder<HttpMethod>()
.caseSensitive(true)
.withAll(HttpMethod.values(), HttpMethod::asString)
.build();
public static final Index<HttpMethod> LOOK_AHEAD = new Index.Builder<HttpMethod>()
.caseSensitive(true)
.withAll(HttpMethod.values(), httpMethod -> httpMethod.asString() + ' ')
.build();
public static final int ACL_AS_INT = ('A' & 0xff) << 24 | ('C' & 0xFF) << 16 | ('L' & 0xFF) << 8 | (' ' & 0xFF);
public static final int GET_AS_INT = ('G' & 0xff) << 24 | ('E' & 0xFF) << 16 | ('T' & 0xFF) << 8 | (' ' & 0xFF);
public static final int PRI_AS_INT = ('P' & 0xff) << 24 | ('R' & 0xFF) << 16 | ('I' & 0xFF) << 8 | (' ' & 0xFF);
public static final int PUT_AS_INT = ('P' & 0xff) << 24 | ('U' & 0xFF) << 16 | ('T' & 0xFF) << 8 | (' ' & 0xFF);
public static final int POST_AS_INT = ('P' & 0xff) << 24 | ('O' & 0xFF) << 16 | ('S' & 0xFF) << 8 | ('T' & 0xFF);
public static final int HEAD_AS_INT = ('H' & 0xff) << 24 | ('E' & 0xFF) << 16 | ('A' & 0xFF) << 8 | ('D' & 0xFF);
static
{
for (HttpMethod method : HttpMethod.values())
{
if (!INSENSITIVE_CACHE.put(method.asString(), method))
throw new IllegalStateException("INSENSITIVE_CACHE too small: " + method);

if (!CACHE.put(method.asString(), method))
throw new IllegalStateException("CACHE too small: " + method);

if (!LOOK_AHEAD.put(method.asString() + ' ', method))
throw new IllegalStateException("LOOK_AHEAD too small: " + method);
}
}

/**
* Optimized lookup to find a method name and trailing space in a byte array.
Expand Down
148 changes: 83 additions & 65 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -67,7 +67,7 @@
* </p>
* <p>
* For performance, the parse is heavily dependent on the
* {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
* {@link Index#getBest(ByteBuffer, int, int)} method to look ahead in a
* single pass for both the structure ( : and CRLF ) and semantic (which
* header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
* is used to lookup common combinations of headers and values
Expand Down Expand Up @@ -106,8 +106,74 @@ public class HttpParser
* determine the header name even if the name:value combination is not cached
* </ul>
*/
public static final Trie<HttpField> CACHE = new ArrayTrie<>(2048);
private static final Trie<HttpField> NO_CACHE = Trie.empty(true);
public static final Index<HttpField> CACHE = new Index.Builder<HttpField>()
.caseSensitive(false)
.with(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE))
.with(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE))
.with(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.UPGRADE))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip"))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate"))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate, br"))
.with(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip,deflate,sdch"))
.with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,enq=0.5"))
.with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-USq=0.8,enq=0.6"))
.with(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-AU,enq=0.9,it-ITq=0.8,itq=0.7,en-GBq=0.6,en-USq=0.5"))
.with(new HttpField(HttpHeader.ACCEPT_CHARSET, "ISO-8859-1,utf-8q=0.7,*q=0.3"))
.with(new HttpField(HttpHeader.ACCEPT, "*/*"))
.with(new HttpField(HttpHeader.ACCEPT, "image/png,image/*q=0.8,*/*q=0.5"))
.with(new HttpField(HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xmlq=0.9,*/*q=0.8"))
.with(new HttpField(HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xmlq=0.9,image/webp,image/apng,*/*q=0.8"))
.with(new HttpField(HttpHeader.ACCEPT_RANGES, HttpHeaderValue.BYTES))
.with(new HttpField(HttpHeader.PRAGMA, "no-cache"))
.with(new HttpField(HttpHeader.CACHE_CONTROL, "private, no-cache, no-cache=Set-Cookie, proxy-revalidate"))
.with(new HttpField(HttpHeader.CACHE_CONTROL, "no-cache"))
.with(new HttpField(HttpHeader.CACHE_CONTROL, "max-age=0"))
.with(new HttpField(HttpHeader.CONTENT_LENGTH, "0"))
.with(new HttpField(HttpHeader.CONTENT_ENCODING, "gzip"))
.with(new HttpField(HttpHeader.CONTENT_ENCODING, "deflate"))
.with(new HttpField(HttpHeader.TRANSFER_ENCODING, "chunked"))
.with(new HttpField(HttpHeader.EXPIRES, "Fri, 01 Jan 1990 00:00:00 GMT"))
.withAll(() ->
{
Map<String, HttpField> map = new LinkedHashMap<>();
// Add common Content types as fields
for (String type : new String[]{
"text/plain", "text/html", "text/xml", "text/json", "application/json", "application/x-www-form-urlencoded"
})
{
HttpField field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type);
map.put(field.toString(), field);

for (String charset : new String[]{"utf-8", "iso-8859-1"})
{
PreEncodedHttpField field1 = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset);
map.put(field1.toString(), field1);
PreEncodedHttpField field2 = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset);
map.put(field2.toString(), field2);
PreEncodedHttpField field3 = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset.toUpperCase(Locale.ENGLISH));
map.put(field3.toString(), field3);
PreEncodedHttpField field4 = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset.toUpperCase(Locale.ENGLISH));
map.put(field4.toString(), field4);
}
}
return map;
})
.withAll(() ->
{
Map<String, HttpField> map = new LinkedHashMap<>();
for (HttpHeader h : HttpHeader.values())
{
HttpField httpField = new HttpField(h, (String)null);
map.put(httpField.toString(), httpField);
}
return map;
})
.build();
private static final Index.Mutable<HttpField> NO_CACHE = new Index.Builder<HttpField>()
.caseSensitive(false)
.mutable()
.maxCapacity(0)
.build();

// States
public enum FieldState
Expand Down Expand Up @@ -181,65 +247,12 @@ public enum State
private boolean _headResponse;
private boolean _cr;
private ByteBuffer _contentChunk;
private Trie<HttpField> _fieldCache;
private Index.Mutable<HttpField> _fieldCache;
private int _length;
private final StringBuilder _string = new StringBuilder();
private int _headerCacheSize = 1024;
private boolean _headerCacheCaseSensitive;

static
{
CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE));
CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE));
CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.UPGRADE));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate, br"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip,deflate,sdch"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-US;q=0.8,en;q=0.6"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-AU,en;q=0.9,it-IT;q=0.8,it;q=0.7,en-GB;q=0.6,en-US;q=0.5"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
CACHE.put(new HttpField(HttpHeader.ACCEPT, "*/*"));
CACHE.put(new HttpField(HttpHeader.ACCEPT, "image/png,image/*;q=0.8,*/*;q=0.5"));
CACHE.put(new HttpField(HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
CACHE.put(new HttpField(HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_RANGES, HttpHeaderValue.BYTES));
CACHE.put(new HttpField(HttpHeader.PRAGMA, "no-cache"));
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "no-cache"));
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "max-age=0"));
CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH, "0"));
CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING, "gzip"));
CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING, "deflate"));
CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING, "chunked"));
CACHE.put(new HttpField(HttpHeader.EXPIRES, "Fri, 01 Jan 1990 00:00:00 GMT"));

// Add common Content types as fields
for (String type : new String[]{
"text/plain", "text/html", "text/xml", "text/json", "application/json", "application/x-www-form-urlencoded"
})
{
HttpField field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type);
CACHE.put(field);

for (String charset : new String[]{"utf-8", "iso-8859-1"})
{
CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset));
CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset));
CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset.toUpperCase(Locale.ENGLISH)));
CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset.toUpperCase(Locale.ENGLISH)));
}
}

// Add headers with null values so HttpParser can avoid looking up name again for unknown values
for (HttpHeader h : HttpHeader.values())
{
if (!h.isPseudo() && !CACHE.put(new HttpField(h, (String)null)))
throw new IllegalStateException("CACHE FULL");
}
}

private static HttpCompliance compliance()
{
return RFC7230;
Expand Down Expand Up @@ -1052,14 +1065,19 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
if (_fieldCache == null)
{
_fieldCache = (getHeaderCacheSize() > 0 && (_version != null && _version == HttpVersion.HTTP_1_1))
? new ArrayTernaryTrie<>(getHeaderCacheSize())
? new Index.Builder<HttpField>()
.caseSensitive(false)
.mutable()
.maxCapacity(getHeaderCacheSize())
.build()
: NO_CACHE;
}

if (!_fieldCache.isFull())
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (!_fieldCache.put(_field))
{
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
_fieldCache.clear();
_fieldCache.put(_field);
}
}
Expand Down Expand Up @@ -1898,7 +1916,7 @@ protected void setState(FieldState state)
_fieldState = state;
}

public Trie<HttpField> getFieldCache()
public Index<HttpField> getFieldCache()
{
return _fieldCache;
}
Expand Down
Loading

0 comments on commit 981e263

Please sign in to comment.