Skip to content

Commit

Permalink
Reduce header cache memory usage on non persistent requests (#6494)
Browse files Browse the repository at this point in the history
Delay creating a header cache until a second request on a parser.
Refactored cache code into subclass

Signed-off-by: Greg Wilkins <gregw@webtide.com>
  • Loading branch information
gregw committed Jul 7, 2021
1 parent 259f9af commit 8945a58
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 26 deletions.
124 changes: 102 additions & 22 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
Expand Up @@ -15,6 +15,7 @@

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -217,6 +218,7 @@ public enum State
private final int _maxHeaderBytes;
private final HttpCompliance _complianceMode;
private final Utf8StringBuilder _uri = new Utf8StringBuilder(INITIAL_URI_LENGTH);
private final FieldCache _fieldCache = new FieldCache();
private HttpField _field;
private HttpHeader _header;
private String _headerString;
Expand All @@ -241,11 +243,8 @@ public enum State
private boolean _headResponse;
private boolean _cr;
private ByteBuffer _contentChunk;
private Index.Mutable<HttpField> _fieldCache;
private int _length;
private final StringBuilder _string = new StringBuilder();
private int _headerCacheSize = 1024;
private boolean _headerCacheCaseSensitive;

private static HttpCompliance compliance()
{
Expand Down Expand Up @@ -304,22 +303,22 @@ public HttpHandler getHandler()

public int getHeaderCacheSize()
{
return _headerCacheSize;
return _fieldCache.getCapacity();
}

public void setHeaderCacheSize(int headerCacheSize)
{
_headerCacheSize = headerCacheSize;
_fieldCache.setCapacity(headerCacheSize);
}

public boolean isHeaderCacheCaseSensitive()
{
return _headerCacheCaseSensitive;
return _fieldCache.isCaseSensitive();
}

public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
{
_headerCacheCaseSensitive = headerCacheCaseSensitive;
_fieldCache.setCaseSensitive(headerCacheCaseSensitive);
}

protected void checkViolation(Violation violation) throws BadMessageException
Expand Down Expand Up @@ -746,6 +745,7 @@ private boolean parseLine(ByteBuffer buffer)
break;

case LF:
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, null);
break;
Expand Down Expand Up @@ -851,6 +851,7 @@ else if (n == HttpTokens.LINE_FEED)
case LF:
if (_responseHandler != null)
{
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, null);
}
Expand Down Expand Up @@ -881,7 +882,7 @@ else if (n == HttpTokens.LINE_FEED)
_version = HttpVersion.CACHE.get(takeString());
}
checkVersion();

_fieldCache.prepare();
setState(State.HEADER);

_requestHandler.startRequest(_methodString, _uri.toString(), _version);
Expand All @@ -905,6 +906,7 @@ else if (n == HttpTokens.LINE_FEED)
{
case LF:
String reason = takeString();
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, reason);
continue;
Expand Down Expand Up @@ -1026,7 +1028,7 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
_field = new HostPortHttpField(_header,
CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode) ? _headerString : _header.asString(),
_valueString);
addToFieldCache = true;
addToFieldCache = _fieldCache.isEnabled();
}
break;

Expand All @@ -1035,7 +1037,7 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (getHeaderCacheSize() > 0 && _field.contains(HttpHeaderValue.CLOSE.asString()))
_fieldCache = NO_CACHE;
_fieldCache.setCapacity(-1);
break;

case AUTHORIZATION:
Expand All @@ -1046,26 +1048,20 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
case COOKIE:
case CACHE_CONTROL:
case USER_AGENT:
addToFieldCache = _field == null;
addToFieldCache = _field == null && _fieldCache.cacheable(_header, _valueString);
break;

default:
break;
}

// Cache field?
if (addToFieldCache && _header != null && _valueString != null)
if (addToFieldCache)
{
if (_fieldCache == null)
_fieldCache = Index.buildCaseSensitiveMutableVisibleAsciiAlphabet(getHeaderCacheSize());

if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (_field.getValue().length() < getHeaderCacheSize() && !_fieldCache.put(_field))
{
_fieldCache.clear();
_fieldCache.put(_field);
}

_fieldCache.add(_field);
}
}
_handler.parsedHeader(_field != null ? _field : new HttpField(_header, _headerString, _valueString));
Expand Down Expand Up @@ -1244,7 +1240,7 @@ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
HttpField cachedField = _fieldCache == null ? null : _fieldCache.getBest(buffer, -1, buffer.remaining());
HttpField cachedField = _fieldCache.getBest(buffer, -1, buffer.remaining());
if (cachedField == null)
cachedField = CACHE.getBest(buffer, -1, buffer.remaining());

Expand Down Expand Up @@ -1911,7 +1907,8 @@ protected void setState(FieldState state)

public Index<HttpField> getFieldCache()
{
return _fieldCache;
_fieldCache.prepare();
return _fieldCache.getCache();
}

@Override
Expand Down Expand Up @@ -2007,4 +2004,87 @@ private IllegalCharacterException(State state, HttpTokens.Token token, ByteBuffe
LOG.debug(String.format("Illegal character %s in state=%s for buffer %s", token, state, BufferUtil.toDetailString(buffer)));
}
}

private static class FieldCache
{
private int _size = 1024;
private Index.Mutable<HttpField> _cache;
private List<HttpField> _cacheableFields;
private boolean _caseSensitive;

public int getCapacity()
{
return _size;
}

public void setCapacity(int size)
{
_size = size;
if (_size <= 0)
_cache = NO_CACHE;
else
_cache = null;
}

public boolean isCaseSensitive()
{
return _caseSensitive;
}

public void setCaseSensitive(boolean caseSensitive)
{
_caseSensitive = caseSensitive;
}

public boolean isEnabled()
{
return _cache != NO_CACHE;
}

public Index<HttpField> getCache()
{
return _cache;
}

public HttpField getBest(ByteBuffer buffer, int i, int remaining)
{
Index.Mutable<HttpField> cache = _cache;
return cache == null ? null : _cache.getBest(buffer, i, remaining);
}

public void add(HttpField field)
{
if (_cache == null)
{
if (_cacheableFields == null)
_cacheableFields = new ArrayList<>();
_cacheableFields.add(field);
}
else if (!_cache.put(field))
{
_cache.clear();
_cache.put(field);
}
}

public boolean cacheable(HttpHeader header, String valueString)
{
return isEnabled() && header != null && valueString.length() <= _size;
}

private void prepare()
{
if (_cache == null && _cacheableFields != null)
{
_cache = Index.buildMutableVisibleAsciiAlphabet(_caseSensitive, _size);
for (HttpField f : _cacheableFields)
{
if (!_cache.put(f))
break;
}
_cacheableFields.clear();
_cacheableFields = null;
}
}
}
}
8 changes: 4 additions & 4 deletions jetty-util/src/main/java/org/eclipse/jetty/util/Index.java
Expand Up @@ -259,13 +259,13 @@ public Mutable<V> build()
* @param <V> The type of the index
* @return A case sensitive mutable Index tacking visible ASCII alphabet to a max capacity.
*/
static <V> Mutable<V> buildCaseSensitiveMutableVisibleAsciiAlphabet(int maxCapacity)
static <V> Mutable<V> buildMutableVisibleAsciiAlphabet(boolean caseSensitive, int maxCapacity)
{
if (maxCapacity < 0 || maxCapacity > ArrayTrie.MAX_CAPACITY)
return new TreeTrie<>(true);
return new TreeTrie<>(caseSensitive);
if (maxCapacity == 0)
return EmptyTrie.instance(true);
return new ArrayTrie<>(true, maxCapacity);
return EmptyTrie.instance(caseSensitive);
return new ArrayTrie<>(caseSensitive, maxCapacity);
}

static <V> Index<V> empty(boolean caseSensitive)
Expand Down

0 comments on commit 8945a58

Please sign in to comment.