diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 52844a5d9eb2..dad562a72e41 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -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; @@ -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; @@ -241,11 +243,8 @@ public enum State private boolean _headResponse; private boolean _cr; private ByteBuffer _contentChunk; - private Index.Mutable _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 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 @@ -746,6 +745,7 @@ private boolean parseLine(ByteBuffer buffer) break; case LF: + _fieldCache.prepare(); setState(State.HEADER); _responseHandler.startResponse(_version, _responseStatus, null); break; @@ -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); } @@ -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); @@ -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; @@ -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; @@ -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: @@ -1046,7 +1048,7 @@ 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: @@ -1054,18 +1056,12 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT) } // 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 @@ 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()); @@ -1911,7 +1907,8 @@ protected void setState(FieldState state) public Index getFieldCache() { - return _fieldCache; + _fieldCache.prepare(); + return _fieldCache.getCache(); } @Override @@ -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 _cache; + private List _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 getCache() + { + return _cache; + } + + public HttpField getBest(ByteBuffer buffer, int i, int remaining) + { + Index.Mutable 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; + } + } + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java index 1b79b0fa21c5..2e4e0656dacd 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Index.java @@ -259,13 +259,13 @@ public Mutable build() * @param The type of the index * @return A case sensitive mutable Index tacking visible ASCII alphabet to a max capacity. */ - static Mutable buildCaseSensitiveMutableVisibleAsciiAlphabet(int maxCapacity) + static Mutable 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 Index empty(boolean caseSensitive)