Reduce header cache memory usage on non persistent requests (#6494)

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>
This commit is contained in:
Greg Wilkins 2021-07-07 12:51:06 +10:00 committed by GitHub
parent 259f9af9c9
commit 8945a58ffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 26 deletions

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.http;
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;
@ -217,6 +218,7 @@ public class HttpParser
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;
@ -241,11 +243,8 @@ public class HttpParser
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()
{
@ -304,22 +303,22 @@ public class HttpParser
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
@ -746,6 +745,7 @@ public class HttpParser
break;
case LF:
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, null);
break;
@ -851,6 +851,7 @@ public class HttpParser
case LF:
if (_responseHandler != null)
{
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, null);
}
@ -881,7 +882,7 @@ public class HttpParser
_version = HttpVersion.CACHE.get(takeString());
}
checkVersion();
_fieldCache.prepare();
setState(State.HEADER);
_requestHandler.startRequest(_methodString, _uri.toString(), _version);
@ -905,6 +906,7 @@ public class HttpParser
{
case LF:
String reason = takeString();
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, reason);
continue;
@ -1026,7 +1028,7 @@ public class HttpParser
_field = new HostPortHttpField(_header,
CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode) ? _headerString : _header.asString(),
_valueString);
addToFieldCache = true;
addToFieldCache = _fieldCache.isEnabled();
}
break;
@ -1035,7 +1037,7 @@ public class HttpParser
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:
@ -1046,7 +1048,7 @@ public class HttpParser
case COOKIE:
case CACHE_CONTROL:
case USER_AGENT:
addToFieldCache = _field == null;
addToFieldCache = _field == null && _fieldCache.cacheable(_header, _valueString);
break;
default:
@ -1054,18 +1056,12 @@ public class HttpParser
}
// 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));
@ -1244,7 +1240,7 @@ public class HttpParser
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());
@ -1911,7 +1907,8 @@ public class HttpParser
public Index<HttpField> getFieldCache()
{
return _fieldCache;
_fieldCache.prepare();
return _fieldCache.getCache();
}
@Override
@ -2007,4 +2004,87 @@ public class HttpParser
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;
}
}
}
}

View File

@ -259,13 +259,13 @@ public interface Index<V>
* @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)