Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #5562 Improve HTTP Field cache allocation #5565

Merged
merged 9 commits into from Nov 12, 2020
24 changes: 18 additions & 6 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java
Expand Up @@ -128,13 +128,13 @@ public enum HttpHeader
/**
* HTTP2 Fields.
*/
C_METHOD(":method"),
C_SCHEME(":scheme"),
C_AUTHORITY(":authority"),
C_PATH(":path"),
C_STATUS(":status"),
C_METHOD(":method", true),
C_SCHEME(":scheme", true),
C_AUTHORITY(":authority", true),
C_PATH(":path", true),
C_STATUS(":status", true),

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

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

Expand All @@ -153,14 +153,21 @@ public enum HttpHeader
private final byte[] _bytes;
private final byte[] _bytesColonSpace;
private final ByteBuffer _buffer;
private final boolean _pseudo;

HttpHeader(String s)
{
this(s, false);
}

HttpHeader(String s, boolean pseudo)
{
_string = s;
_lowerCase = StringUtil.asciiToLowerCase(s);
_bytes = StringUtil.getBytes(s);
_bytesColonSpace = StringUtil.getBytes(s + ": ");
_buffer = ByteBuffer.wrap(_bytes);
_pseudo = pseudo;
}

public String lowerCaseName()
Expand Down Expand Up @@ -188,6 +195,11 @@ public boolean is(String s)
return _string.equalsIgnoreCase(s);
}

public boolean isPseudo()
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
return _pseudo;
}

public String asString()
{
return _string;
Expand Down
40 changes: 24 additions & 16 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
Expand Up @@ -25,6 +25,7 @@
import java.util.Locale;

import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.AbstractTrie;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
Expand Down Expand Up @@ -103,6 +104,7 @@ public class HttpParser
* </ul>
*/
public static final Trie<HttpField> CACHE = new ArrayTrie<>(2048);
private static final Trie<HttpField> NO_CACHE = AbstractTrie.emptyTrie(true);

// States
public enum FieldState
Expand Down Expand Up @@ -152,6 +154,7 @@ public enum State
private final int _maxHeaderBytes;
private final HttpCompliance _compliance;
private final EnumSet<HttpComplianceSection> _compliances;
private final Utf8StringBuilder _uri = new Utf8StringBuilder(INITIAL_URI_LENGTH);
private HttpField _field;
private HttpHeader _header;
private String _headerString;
Expand All @@ -167,7 +170,6 @@ public enum State
private HttpMethod _method;
private String _methodString;
private HttpVersion _version;
private Utf8StringBuilder _uri = new Utf8StringBuilder(INITIAL_URI_LENGTH); // Tune?
private EndOfContent _endOfContent;
private boolean _hasContentLength;
private boolean _hasTransferEncoding;
Expand Down Expand Up @@ -231,7 +233,7 @@ public enum State
// Add headers with null values so HttpParser can avoid looking up name again for unknown values
for (HttpHeader h : HttpHeader.values())
{
if (!CACHE.put(new HttpField(h, (String)null)))
if (!h.isPseudo() && !CACHE.put(new HttpField(h, (String)null)))
throw new IllegalStateException("CACHE FULL");
}
}
Expand Down Expand Up @@ -884,11 +886,6 @@ else if (n == HttpTokens.LINE_FEED)
}
checkVersion();

// Should we try to cache header fields?
int headerCache = _handler.getHeaderCacheSize();
if (_fieldCache == null && _version.getVersion() >= HttpVersion.HTTP_1_1.getVersion() && headerCache > 0)
_fieldCache = new ArrayTernaryTrie<>(headerCache);

setState(State.HEADER);

_requestHandler.startRequest(_methodString, _uri.toString(), _version);
Expand Down Expand Up @@ -961,7 +958,7 @@ private void parsedHeader()
// Handle known headers
if (_header != null)
{
boolean addToConnectionTrie = false;
boolean addToFieldCache = false;
switch (_header)
{
case CONTENT_LENGTH:
Expand Down Expand Up @@ -1034,14 +1031,14 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
_field = new HostPortHttpField(_header,
_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE) ? _header.asString() : _headerString,
_valueString);
addToConnectionTrie = _fieldCache != null;
addToFieldCache = true;
}
break;

case CONNECTION:
// Don't cache headers if not persistent
if (HttpHeaderValue.CLOSE.is(_valueString) || new QuotedCSV(_valueString).getValues().stream().anyMatch(HttpHeaderValue.CLOSE::is))
_fieldCache = null;
if (_handler.getHeaderCacheSize() > 0 && (HttpHeaderValue.CLOSE.is(_valueString) || _valueString.contains(HttpHeaderValue.CLOSE.asString())))
gregw marked this conversation as resolved.
Show resolved Hide resolved
_fieldCache = NO_CACHE;
break;

case AUTHORIZATION:
Expand All @@ -1052,18 +1049,29 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
case COOKIE:
case CACHE_CONTROL:
case USER_AGENT:
addToConnectionTrie = _fieldCache != null && _field == null;
addToFieldCache = _field == null;
break;

default:
break;
}

if (addToConnectionTrie && !_fieldCache.isFull() && _header != null && _valueString != null)
// Cache field?
if (addToFieldCache && _header != null && _valueString != null)
{
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
_fieldCache.put(_field);
if (_fieldCache == null)
{
_fieldCache = (_handler.getHeaderCacheSize() > 0 && (_version != null && _version.getVersion() == HttpVersion.HTTP_1_1.getVersion()))
gregw marked this conversation as resolved.
Show resolved Hide resolved
? new ArrayTernaryTrie<>(_handler.getHeaderCacheSize())
gregw marked this conversation as resolved.
Show resolved Hide resolved
: NO_CACHE;
}

if (!_fieldCache.isFull())
{
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
_fieldCache.put(_field);
}
}
}
_handler.parsedHeader(_field != null ? _field : new HttpField(_header, _headerString, _valueString));
Expand Down
54 changes: 54 additions & 0 deletions jetty-util/src/main/java/org/eclipse/jetty/util/AbstractTrie.java
Expand Up @@ -20,6 +20,7 @@

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
* Abstract Trie implementation.
Expand Down Expand Up @@ -81,4 +82,57 @@ public boolean isCaseInsensitive()
{
return _caseInsensitive;
}

public static <T> Trie<T> emptyTrie(boolean caseInsensitive)
{
return new AbstractTrie<T>(caseInsensitive)
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
@Override
public boolean put(String s, T t)
{
return false;
}

@Override
public T get(String s, int offset, int len)
{
return null;
}

@Override
public T get(ByteBuffer b, int offset, int len)
{
return null;
}

@Override
public T getBest(String s, int offset, int len)
{
return null;
}

@Override
public T getBest(ByteBuffer b, int offset, int len)
{
return null;
}

@Override
public Set<String> keySet()
{
return null;
}

@Override
public boolean isFull()
{
return true;
}

@Override
public void clear()
{
}
};
}
}