Merged branch 'jetty-10.0.x' into 'jetty-11.0.x'.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2020-11-26 16:38:18 +01:00
commit fa232a4684
53 changed files with 3620 additions and 1789 deletions

View File

@ -26,10 +26,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -92,13 +91,11 @@ public class HttpGenerator
private final int _send; private final int _send;
private static final int SEND_SERVER = 0x01; private static final int SEND_SERVER = 0x01;
private static final int SEND_XPOWEREDBY = 0x02; private static final int SEND_XPOWEREDBY = 0x02;
private static final Trie<Boolean> ASSUMED_CONTENT_METHODS = new ArrayTrie<>(8); private static final Index<Boolean> ASSUMED_CONTENT_METHODS = new Index.Builder<Boolean>()
.caseSensitive(false)
static .with(HttpMethod.POST.asString(), Boolean.TRUE)
{ .with(HttpMethod.PUT.asString(), Boolean.TRUE)
ASSUMED_CONTENT_METHODS.put(HttpMethod.POST.asString(), Boolean.TRUE); .build();
ASSUMED_CONTENT_METHODS.put(HttpMethod.PUT.asString(), Boolean.TRUE);
}
public static void setJettyVersion(String serverVersion) public static void setJettyVersion(String serverVersion)
{ {
@ -679,7 +676,7 @@ public class HttpGenerator
// Calculate how to end _content and connection, _content length and transfer encoding // Calculate how to end _content and connection, _content length and transfer encoding
// settings from http://tools.ietf.org/html/rfc7230#section-3.3.3 // 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 assumedContent = assumedContentRequest || contentType || chunkedHint;
boolean noContentRequest = request != null && contentLength <= 0 && !assumedContent; boolean noContentRequest = request != null && contentLength <= 0 && !assumedContent;

View File

@ -20,9 +20,8 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; 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.StringUtil;
import org.eclipse.jetty.util.Trie;
public enum HttpHeader public enum HttpHeader
{ {
@ -133,21 +132,12 @@ public enum HttpHeader
C_AUTHORITY(":authority", true), C_AUTHORITY(":authority", true),
C_PATH(":path", true), C_PATH(":path", true),
C_STATUS(":status", true), C_STATUS(":status", true),
C_PROTOCOL(":protocol"), C_PROTOCOL(":protocol");
UNKNOWN("::UNKNOWN::", true); public static final Index<HttpHeader> CACHE = new Index.Builder<HttpHeader>()
.caseSensitive(false)
public static final Trie<HttpHeader> CACHE = new ArrayTrie<>(630); .withAll(HttpHeader.values(), HttpHeader::toString)
.build();
static
{
for (HttpHeader header : HttpHeader.values())
{
if (header != UNKNOWN)
if (!CACHE.put(header.toString(), header))
throw new IllegalStateException();
}
}
private final String _string; private final String _string;
private final String _lowerCase; private final String _lowerCase;

View File

@ -21,9 +21,8 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.EnumSet; import java.util.EnumSet;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.Index;
/** /**
* *
@ -40,19 +39,12 @@ public enum HttpHeaderValue
TE("TE"), TE("TE"),
BYTES("bytes"), BYTES("bytes"),
NO_CACHE("no-cache"), NO_CACHE("no-cache"),
UPGRADE("Upgrade"), UPGRADE("Upgrade");
UNKNOWN("::UNKNOWN::");
public static final Trie<HttpHeaderValue> CACHE = new ArrayTrie<HttpHeaderValue>(); public static final Index<HttpHeaderValue> CACHE = new Index.Builder<HttpHeaderValue>()
.caseSensitive(false)
static .withAll(HttpHeaderValue.values(), HttpHeaderValue::toString)
{ .build();
for (HttpHeaderValue value : HttpHeaderValue.values())
{
if (value != UNKNOWN)
CACHE.put(value.toString(), value);
}
}
private final String _string; private final String _string;
private final ByteBuffer _buffer; private final ByteBuffer _buffer;

View File

@ -20,10 +20,8 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
/** /**
* Known HTTP Methods * Known HTTP Methods
@ -142,29 +140,24 @@ public enum HttpMethod
return _method; return _method;
} }
public static final Trie<HttpMethod> INSENSITIVE_CACHE = new ArrayTrie<>(252); public static final Index<HttpMethod> INSENSITIVE_CACHE = new Index.Builder<HttpMethod>()
public static final Trie<HttpMethod> CACHE = new ArrayTernaryTrie<>(false, 300); .caseSensitive(false)
public static final Trie<HttpMethod> LOOK_AHEAD = new ArrayTernaryTrie<>(false, 330); .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 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 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 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 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 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); 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. * Optimized lookup to find a method name and trailing space in a byte array.

View File

@ -21,15 +21,15 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import org.eclipse.jetty.http.HttpTokens.EndOfContent; 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.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.Utf8StringBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -67,7 +67,7 @@ import static org.eclipse.jetty.http.HttpCompliance.Violation.WHITESPACE_AFTER_F
* </p> * </p>
* <p> * <p>
* For performance, the parse is heavily dependent on the * 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 * single pass for both the structure ( : and CRLF ) and semantic (which
* header and value) of a header. Specifically the static {@link HttpHeader#CACHE} * header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
* is used to lookup common combinations of headers and values * is used to lookup common combinations of headers and values
@ -106,8 +106,74 @@ public class HttpParser
* determine the header name even if the name:value combination is not cached * determine the header name even if the name:value combination is not cached
* </ul> * </ul>
*/ */
public static final Trie<HttpField> CACHE = new ArrayTrie<>(2048); public static final Index<HttpField> CACHE = new Index.Builder<HttpField>()
private static final Trie<HttpField> NO_CACHE = Trie.empty(true); .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 // States
public enum FieldState public enum FieldState
@ -181,65 +247,12 @@ public class HttpParser
private boolean _headResponse; private boolean _headResponse;
private boolean _cr; private boolean _cr;
private ByteBuffer _contentChunk; private ByteBuffer _contentChunk;
private Trie<HttpField> _fieldCache; private Index.Mutable<HttpField> _fieldCache;
private int _length; private int _length;
private final StringBuilder _string = new StringBuilder(); private final StringBuilder _string = new StringBuilder();
private int _headerCacheSize = 1024; private int _headerCacheSize = 1024;
private boolean _headerCacheCaseSensitive; 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() private static HttpCompliance compliance()
{ {
return RFC7230; return RFC7230;
@ -1052,14 +1065,19 @@ public class HttpParser
if (_fieldCache == null) if (_fieldCache == null)
{ {
_fieldCache = (getHeaderCacheSize() > 0 && (_version != null && _version == HttpVersion.HTTP_1_1)) _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; : NO_CACHE;
} }
if (!_fieldCache.isFull()) if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (!_fieldCache.put(_field))
{ {
if (_field == null) _fieldCache.clear();
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
_fieldCache.put(_field); _fieldCache.put(_field);
} }
} }
@ -1898,7 +1916,7 @@ public class HttpParser
_fieldState = state; _fieldState = state;
} }
public Trie<HttpField> getFieldCache() public Index<HttpField> getFieldCache()
{ {
return _fieldCache; return _fieldCache;
} }

View File

@ -20,9 +20,8 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.Index;
/** /**
* HTTP and WebSocket Schemes * HTTP and WebSocket Schemes
@ -34,15 +33,10 @@ public enum HttpScheme
WS("ws", 80), WS("ws", 80),
WSS("wss", 443); WSS("wss", 443);
public static final Trie<HttpScheme> CACHE = new ArrayTrie<HttpScheme>(); public static final Index<HttpScheme> CACHE = new Index.Builder<HttpScheme>()
.caseSensitive(false)
static .withAll(HttpScheme.values(), HttpScheme::asString)
{ .build();
for (HttpScheme version : HttpScheme.values())
{
CACHE.put(version.asString(), version);
}
}
private final String _string; private final String _string;
private final ByteBuffer _buffer; private final ByteBuffer _buffer;

View File

@ -20,9 +20,8 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer; 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.StringUtil;
import org.eclipse.jetty.util.Trie;
public enum HttpVersion public enum HttpVersion
{ {
@ -31,15 +30,10 @@ public enum HttpVersion
HTTP_1_1("HTTP/1.1", 11), HTTP_1_1("HTTP/1.1", 11),
HTTP_2("HTTP/2.0", 20); HTTP_2("HTTP/2.0", 20);
public static final Trie<HttpVersion> CACHE = new ArrayTrie<HttpVersion>(); public static final Index<HttpVersion> CACHE = new Index.Builder<HttpVersion>()
.caseSensitive(false)
static .withAll(HttpVersion.values(), HttpVersion::toString)
{ .build();
for (HttpVersion version : HttpVersion.values())
{
CACHE.put(version.toString(), version);
}
}
/** /**
* Optimised lookup to find an Http Version and whitespace in a byte array. * Optimised lookup to find an Http Version and whitespace in a byte array.

View File

@ -32,10 +32,9 @@ import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -46,7 +45,6 @@ public class MimeTypes
{ {
private static final Logger LOG = LoggerFactory.getLogger(MimeTypes.class); private static final Logger LOG = LoggerFactory.getLogger(MimeTypes.class);
private static final Trie<ByteBuffer> TYPES = new ArrayTrie<ByteBuffer>(512);
private static final Map<String, String> __dftMimeMap = new HashMap<String, String>(); private static final Map<String, String> __dftMimeMap = new HashMap<String, String>();
private static final Map<String, String> __inferredEncodings = new HashMap<String, String>(); private static final Map<String, String> __inferredEncodings = new HashMap<String, String>();
private static final Map<String, String> __assumedEncodings = new HashMap<String, String>(); private static final Map<String, String> __assumedEncodings = new HashMap<String, String>();
@ -168,23 +166,30 @@ public class MimeTypes
} }
} }
public static final Trie<MimeTypes.Type> CACHE = new ArrayTrie<>(512); public static final Index<Type> CACHE = new Index.Builder<Type>()
.caseSensitive(false)
.withAll(() ->
{
Map<String, Type> result = new HashMap<>();
for (Type type : Type.values())
{
String key1 = type.toString();
result.put(key1, type);
if (key1.indexOf(";charset=") > 0)
{
String key2 = StringUtil.replace(key1, ";charset=", "; charset=");
result.put(key2, type);
}
}
return result;
})
.build();
static static
{ {
for (MimeTypes.Type type : MimeTypes.Type.values()) for (MimeTypes.Type type : MimeTypes.Type.values())
{ {
CACHE.put(type.toString(), type);
TYPES.put(type.toString(), type.asBuffer());
int charset = type.toString().indexOf(";charset=");
if (charset > 0)
{
String alt = StringUtil.replace(type.toString(), ";charset=", "; charset=");
CACHE.put(alt, type);
TYPES.put(alt, type.asBuffer());
}
if (type.isCharsetAssumed()) if (type.isCharsetAssumed())
__assumedEncodings.put(type.asString(), type.getCharsetString()); __assumedEncodings.put(type.asString(), type.getCharsetString());
} }

View File

@ -28,8 +28,7 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
@ -49,9 +48,18 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
private static final Logger LOG = LoggerFactory.getLogger(PathMappings.class); private static final Logger LOG = LoggerFactory.getLogger(PathMappings.class);
private final Set<MappedResource<E>> _mappings = new TreeSet<>(Comparator.comparing(MappedResource::getPathSpec)); private final Set<MappedResource<E>> _mappings = new TreeSet<>(Comparator.comparing(MappedResource::getPathSpec));
private Trie<MappedResource<E>> _exactMap = new ArrayTernaryTrie<>(false); private final Index.Mutable<MappedResource<E>> _exactMap = new Index.Builder<MappedResource<E>>()
private Trie<MappedResource<E>> _prefixMap = new ArrayTernaryTrie<>(false); .caseSensitive(true)
private Trie<MappedResource<E>> _suffixMap = new ArrayTernaryTrie<>(false); .mutable()
.build();
private final Index.Mutable<MappedResource<E>> _prefixMap = new Index.Builder<MappedResource<E>>()
.caseSensitive(true)
.mutable()
.build();
private final Index.Mutable<MappedResource<E>> _suffixMap = new Index.Builder<MappedResource<E>>()
.caseSensitive(true)
.mutable()
.build();
@Override @Override
public String dump() public String dump()
@ -136,10 +144,9 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
case EXACT: case EXACT:
{ {
int i = path.length(); int i = path.length();
final Trie<MappedResource<E>> exact_map = _exactMap;
while (i >= 0) while (i >= 0)
{ {
MappedResource<E> candidate = exact_map.getBest(path, 0, i); MappedResource<E> candidate = _exactMap.getBest(path, 0, i);
if (candidate == null) if (candidate == null)
break; break;
if (candidate.getPathSpec().matches(path)) if (candidate.getPathSpec().matches(path))
@ -152,10 +159,9 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
case PREFIX_GLOB: case PREFIX_GLOB:
{ {
int i = path.length(); int i = path.length();
final Trie<MappedResource<E>> prefix_map = _prefixMap;
while (i >= 0) while (i >= 0)
{ {
MappedResource<E> candidate = prefix_map.getBest(path, 0, i); MappedResource<E> candidate = _prefixMap.getBest(path, 0, i);
if (candidate == null) if (candidate == null)
break; break;
if (candidate.getPathSpec().matches(path)) if (candidate.getPathSpec().matches(path))
@ -168,10 +174,9 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
case SUFFIX_GLOB: case SUFFIX_GLOB:
{ {
int i = 0; int i = 0;
final Trie<MappedResource<E>> suffix_map = _suffixMap;
while ((i = path.indexOf('.', i + 1)) > 0) while ((i = path.indexOf('.', i + 1)) > 0)
{ {
MappedResource<E> candidate = suffix_map.get(path, i + 1, path.length() - i - 1); MappedResource<E> candidate = _suffixMap.get(path, i + 1, path.length() - i - 1);
if (candidate != null && candidate.getPathSpec().matches(path)) if (candidate != null && candidate.getPathSpec().matches(path))
return candidate; return candidate;
} }
@ -230,24 +235,18 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
{ {
case EXACT: case EXACT:
String exact = pathSpec.getPrefix(); String exact = pathSpec.getPrefix();
while (exact != null && !_exactMap.put(exact, entry)) if (exact != null)
{ _exactMap.put(exact, entry);
_exactMap = new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedResource<E>>)_exactMap, 1.5);
}
break; break;
case PREFIX_GLOB: case PREFIX_GLOB:
String prefix = pathSpec.getPrefix(); String prefix = pathSpec.getPrefix();
while (prefix != null && !_prefixMap.put(prefix, entry)) if (prefix != null)
{ _prefixMap.put(prefix, entry);
_prefixMap = new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedResource<E>>)_prefixMap, 1.5);
}
break; break;
case SUFFIX_GLOB: case SUFFIX_GLOB:
String suffix = pathSpec.getSuffix(); String suffix = pathSpec.getSuffix();
while (suffix != null && !_suffixMap.put(suffix, entry)) if (suffix != null)
{ _suffixMap.put(suffix, entry);
_suffixMap = new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedResource<E>>)_prefixMap, 1.5);
}
break; break;
default: default:
} }

View File

@ -94,20 +94,6 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
this.listener = listener; this.listener = listener;
} }
@Override
public long getMessagesIn()
{
HTTP2ClientSession session = (HTTP2ClientSession)getSession();
return session.getStreamsOpened();
}
@Override
public long getMessagesOut()
{
HTTP2ClientSession session = (HTTP2ClientSession)getSession();
return session.getStreamsClosed();
}
@Override @Override
public void onOpen() public void onOpen()
{ {
@ -127,15 +113,11 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
ISession session = getSession(); ISession session = getSession();
int windowDelta = client.getInitialSessionRecvWindow() - FlowControlStrategy.DEFAULT_WINDOW_SIZE; int windowDelta = client.getInitialSessionRecvWindow() - FlowControlStrategy.DEFAULT_WINDOW_SIZE;
session.updateRecvWindow(windowDelta);
if (windowDelta > 0) if (windowDelta > 0)
{
session.updateRecvWindow(windowDelta);
session.frames(null, List.of(prefaceFrame, settingsFrame, new WindowUpdateFrame(0, windowDelta)), this); session.frames(null, List.of(prefaceFrame, settingsFrame, new WindowUpdateFrame(0, windowDelta)), this);
}
else else
{
session.frames(null, List.of(prefaceFrame, settingsFrame), this); session.frames(null, List.of(prefaceFrame, settingsFrame), this);
}
} }
@Override @Override

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http2.client; package org.eclipse.jetty.http2.client;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.HTTP2Session;
@ -62,7 +63,10 @@ public class HTTP2ClientSession extends HTTP2Session
else else
{ {
stream.process(frame, Callback.NOOP); stream.process(frame, Callback.NOOP);
boolean closed = stream.updateClose(frame.isEndStream(), CloseState.Event.RECEIVED);
notifyHeaders(stream, frame); notifyHeaders(stream, frame);
if (closed)
removeStream(stream);
} }
} }
else else

View File

@ -134,7 +134,7 @@ public class AsyncServletTest extends AbstractTest
HeadersFrame frame = new HeadersFrame(metaData, null, true); HeadersFrame frame = new HeadersFrame(metaData, null, true);
FuturePromise<Stream> promise = new FuturePromise<>(); FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch responseLatch = new CountDownLatch(1); CountDownLatch responseLatch = new CountDownLatch(1);
CountDownLatch resetLatch = new CountDownLatch(1); CountDownLatch failLatch = new CountDownLatch(1);
session.newStream(frame, promise, new Stream.Listener.Adapter() session.newStream(frame, promise, new Stream.Listener.Adapter()
{ {
@Override @Override
@ -144,9 +144,10 @@ public class AsyncServletTest extends AbstractTest
} }
@Override @Override
public void onReset(Stream stream, ResetFrame frame) public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback)
{ {
resetLatch.countDown(); failLatch.countDown();
callback.succeeded();
} }
}); });
Stream stream = promise.get(5, TimeUnit.SECONDS); Stream stream = promise.get(5, TimeUnit.SECONDS);
@ -154,7 +155,7 @@ public class AsyncServletTest extends AbstractTest
assertTrue(serverLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); assertTrue(serverLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
assertFalse(responseLatch.await(idleTimeout + 1000, TimeUnit.MILLISECONDS)); assertFalse(responseLatch.await(idleTimeout + 1000, TimeUnit.MILLISECONDS));
assertTrue(resetLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); assertTrue(failLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
} }
@Test @Test

View File

@ -137,10 +137,10 @@ public abstract class FlowControlStrategyTest
@Test @Test
public void testWindowSizeUpdates() throws Exception public void testWindowSizeUpdates() throws Exception
{ {
final CountDownLatch prefaceLatch = new CountDownLatch(1); CountDownLatch prefaceLatch = new CountDownLatch(1);
final CountDownLatch stream1Latch = new CountDownLatch(1); CountDownLatch stream1Latch = new CountDownLatch(1);
final CountDownLatch stream2Latch = new CountDownLatch(1); CountDownLatch stream2Latch = new CountDownLatch(1);
final CountDownLatch settingsLatch = new CountDownLatch(1); CountDownLatch settingsLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -233,11 +233,11 @@ public abstract class FlowControlStrategyTest
// then we change the window to 512 B. At this point, the client // then we change the window to 512 B. At this point, the client
// must stop sending data (although the initial window allows it). // must stop sending data (although the initial window allows it).
final int size = 512; int size = 512;
// We get 3 data frames: the first of 1024 and 2 of 512 each // We get 3 data frames: the first of 1024 and 2 of 512 each
// after the flow control window has been reduced. // after the flow control window has been reduced.
final CountDownLatch dataLatch = new CountDownLatch(3); CountDownLatch dataLatch = new CountDownLatch(3);
final AtomicReference<Callback> callbackRef = new AtomicReference<>(); AtomicReference<Callback> callbackRef = new AtomicReference<>();
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -275,7 +275,7 @@ public abstract class FlowControlStrategyTest
}); });
// Two SETTINGS frames, the initial one and the one we send from the server. // Two SETTINGS frames, the initial one and the one we send from the server.
final CountDownLatch settingsLatch = new CountDownLatch(2); CountDownLatch settingsLatch = new CountDownLatch(2);
Session session = newClient(new Session.Listener.Adapter() Session session = newClient(new Session.Listener.Adapter()
{ {
@Override @Override
@ -312,9 +312,9 @@ public abstract class FlowControlStrategyTest
@Test @Test
public void testServerFlowControlOneBigWrite() throws Exception public void testServerFlowControlOneBigWrite() throws Exception
{ {
final int windowSize = 1536; int windowSize = 1536;
final int length = 5 * windowSize; int length = 5 * windowSize;
final CountDownLatch settingsLatch = new CountDownLatch(2); CountDownLatch settingsLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -349,13 +349,13 @@ public abstract class FlowControlStrategyTest
assertTrue(settingsLatch.await(5, TimeUnit.SECONDS)); assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
final CountDownLatch dataLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1);
final Exchanger<Callback> exchanger = new Exchanger<>(); Exchanger<Callback> exchanger = new Exchanger<>();
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
{ {
private AtomicInteger dataFrames = new AtomicInteger(); private final AtomicInteger dataFrames = new AtomicInteger();
@Override @Override
public void onData(Stream stream, DataFrame frame, Callback callback) public void onData(Stream stream, DataFrame frame, Callback callback)
@ -406,10 +406,10 @@ public abstract class FlowControlStrategyTest
@Test @Test
public void testClientFlowControlOneBigWrite() throws Exception public void testClientFlowControlOneBigWrite() throws Exception
{ {
final int windowSize = 1536; int windowSize = 1536;
final Exchanger<Callback> exchanger = new Exchanger<>(); Exchanger<Callback> exchanger = new Exchanger<>();
final CountDownLatch settingsLatch = new CountDownLatch(1); CountDownLatch settingsLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -428,7 +428,7 @@ public abstract class FlowControlStrategyTest
stream.headers(responseFrame, Callback.NOOP); stream.headers(responseFrame, Callback.NOOP);
return new Stream.Listener.Adapter() return new Stream.Listener.Adapter()
{ {
private AtomicInteger dataFrames = new AtomicInteger(); private final AtomicInteger dataFrames = new AtomicInteger();
@Override @Override
public void onData(Stream stream, DataFrame frame, Callback callback) public void onData(Stream stream, DataFrame frame, Callback callback)
@ -480,7 +480,7 @@ public abstract class FlowControlStrategyTest
session.newStream(requestFrame, streamPromise, null); session.newStream(requestFrame, streamPromise, null);
Stream stream = streamPromise.get(5, TimeUnit.SECONDS); Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
final int length = 5 * windowSize; int length = 5 * windowSize;
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true); DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true);
stream.data(dataFrame, Callback.NOOP); stream.data(dataFrame, Callback.NOOP);
@ -499,7 +499,7 @@ public abstract class FlowControlStrategyTest
assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
} }
private void checkThatWeAreFlowControlStalled(Exchanger<Callback> exchanger) throws Exception private void checkThatWeAreFlowControlStalled(Exchanger<Callback> exchanger)
{ {
assertThrows(TimeoutException.class, assertThrows(TimeoutException.class,
() -> exchanger.exchange(null, 1, TimeUnit.SECONDS)); () -> exchanger.exchange(null, 1, TimeUnit.SECONDS));
@ -508,7 +508,7 @@ public abstract class FlowControlStrategyTest
@Test @Test
public void testSessionStalledStallsNewStreams() throws Exception public void testSessionStalledStallsNewStreams() throws Exception
{ {
final int windowSize = 1024; int windowSize = 1024;
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -543,8 +543,8 @@ public abstract class FlowControlStrategyTest
Session session = newClient(new Session.Listener.Adapter()); Session session = newClient(new Session.Listener.Adapter());
// First request is just to consume most of the session window. // First request is just to consume most of the session window.
final List<Callback> callbacks1 = new ArrayList<>(); List<Callback> callbacks1 = new ArrayList<>();
final CountDownLatch prepareLatch = new CountDownLatch(1); CountDownLatch prepareLatch = new CountDownLatch(1);
MetaData.Request request1 = newRequest("POST", HttpFields.EMPTY); MetaData.Request request1 = newRequest("POST", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{ {
@ -583,7 +583,7 @@ public abstract class FlowControlStrategyTest
}); });
// Fourth request is now stalled. // Fourth request is now stalled.
final CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
MetaData.Request request4 = newRequest("GET", HttpFields.EMPTY); MetaData.Request request4 = newRequest("GET", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{ {
@ -612,7 +612,7 @@ public abstract class FlowControlStrategyTest
@Test @Test
public void testServerSendsBigContent() throws Exception public void testServerSendsBigContent() throws Exception
{ {
final byte[] data = new byte[1024 * 1024]; byte[] data = new byte[1024 * 1024];
new Random().nextBytes(data); new Random().nextBytes(data);
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
@ -636,8 +636,8 @@ public abstract class FlowControlStrategyTest
Session session = newClient(new Session.Listener.Adapter()); Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
final byte[] bytes = new byte[data.length]; byte[] bytes = new byte[data.length];
final CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
{ {
private int received; private int received;
@ -681,7 +681,7 @@ public abstract class FlowControlStrategyTest
} }
}); });
final int initialWindow = 16; int initialWindow = 16;
Session session = newClient(new Session.Listener.Adapter() Session session = newClient(new Session.Listener.Adapter()
{ {
@Override @Override
@ -697,11 +697,11 @@ public abstract class FlowControlStrategyTest
new Random().nextBytes(requestData); new Random().nextBytes(requestData);
byte[] responseData = new byte[requestData.length]; byte[] responseData = new byte[requestData.length];
final ByteBuffer responseContent = ByteBuffer.wrap(responseData); ByteBuffer responseContent = ByteBuffer.wrap(responseData);
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
Promise.Completable<Stream> completable = new Promise.Completable<>(); Promise.Completable<Stream> completable = new Promise.Completable<>();
final CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, completable, new Stream.Listener.Adapter() session.newStream(requestFrame, completable, new Stream.Listener.Adapter()
{ {
@Override @Override
@ -730,6 +730,7 @@ public abstract class FlowControlStrategyTest
public void testClientExceedingSessionWindow() throws Exception public void testClientExceedingSessionWindow() throws Exception
{ {
// On server, we don't consume the data. // On server, we don't consume the data.
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -744,16 +745,29 @@ public abstract class FlowControlStrategyTest
} }
}; };
} }
});
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override @Override
public void onClose(Session session, GoAwayFrame frame) public void onClose(Session session, GoAwayFrame frame)
{
serverCloseLatch.countDown();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{ {
if (frame.getError() == ErrorCode.FLOW_CONTROL_ERROR.code) if (frame.getError() == ErrorCode.FLOW_CONTROL_ERROR.code)
closeLatch.countDown(); clientGoAwayLatch.countDown();
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
clientCloseLatch.countDown();
} }
}); });
@ -764,7 +778,7 @@ public abstract class FlowControlStrategyTest
session.newStream(requestFrame, Promise.from(completable), new Stream.Listener.Adapter()); session.newStream(requestFrame, Promise.from(completable), new Stream.Listener.Adapter());
Stream stream = completable.get(5, TimeUnit.SECONDS); Stream stream = completable.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
final CountDownLatch dataLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1);
stream.data(new DataFrame(stream.getId(), data, false), new Callback() stream.data(new DataFrame(stream.getId(), data, false), new Callback()
{ {
@Override @Override
@ -796,16 +810,19 @@ public abstract class FlowControlStrategyTest
ByteBuffer extraData = ByteBuffer.allocate(1024); ByteBuffer extraData = ByteBuffer.allocate(1024);
http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining()); http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining());
List<ByteBuffer> buffers = lease.getByteBuffers(); List<ByteBuffer> buffers = lease.getByteBuffers();
http2Session.getEndPoint().write(Callback.NOOP, buffers.toArray(new ByteBuffer[buffers.size()])); http2Session.getEndPoint().write(Callback.NOOP, buffers.toArray(new ByteBuffer[0]));
// Expect the connection to be closed. // Expect the connection to be closed.
assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS));
} }
@Test @Test
public void testClientExceedingStreamWindow() throws Exception public void testClientExceedingStreamWindow() throws Exception
{ {
// On server, we don't consume the data. // On server, we don't consume the data.
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter() start(new ServerSessionListener.Adapter()
{ {
@Override @Override
@ -828,16 +845,29 @@ public abstract class FlowControlStrategyTest
} }
}; };
} }
});
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override @Override
public void onClose(Session session, GoAwayFrame frame) public void onClose(Session session, GoAwayFrame frame)
{
serverCloseLatch.countDown();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{ {
if (frame.getError() == ErrorCode.FLOW_CONTROL_ERROR.code) if (frame.getError() == ErrorCode.FLOW_CONTROL_ERROR.code)
closeLatch.countDown(); clientGoAwayLatch.countDown();
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
clientCloseLatch.countDown();
} }
}); });
@ -848,7 +878,7 @@ public abstract class FlowControlStrategyTest
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()); session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
Stream stream = streamPromise.get(5, TimeUnit.SECONDS); Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
final CountDownLatch dataLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1);
stream.data(new DataFrame(stream.getId(), data, false), new Callback() stream.data(new DataFrame(stream.getId(), data, false), new Callback()
{ {
@Override @Override
@ -876,10 +906,12 @@ public abstract class FlowControlStrategyTest
ByteBuffer extraData = ByteBuffer.allocate(1024); ByteBuffer extraData = ByteBuffer.allocate(1024);
http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining()); http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining());
List<ByteBuffer> buffers = lease.getByteBuffers(); List<ByteBuffer> buffers = lease.getByteBuffers();
http2Session.getEndPoint().write(Callback.NOOP, buffers.toArray(new ByteBuffer[buffers.size()])); http2Session.getEndPoint().write(Callback.NOOP, buffers.toArray(new ByteBuffer[0]));
// Expect the connection to be closed. // Expect the connection to be closed.
assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
assertTrue(serverCloseLatch.await(5, TimeUnit.SECONDS));
} }
@Test @Test
@ -916,7 +948,7 @@ public abstract class FlowControlStrategyTest
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false); HeadersFrame frame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>(); FuturePromise<Stream> streamPromise = new FuturePromise<>();
final CountDownLatch resetLatch = new CountDownLatch(1); CountDownLatch resetLatch = new CountDownLatch(1);
session.newStream(frame, streamPromise, new Stream.Listener.Adapter() session.newStream(frame, streamPromise, new Stream.Listener.Adapter()
{ {
@Override @Override
@ -929,7 +961,7 @@ public abstract class FlowControlStrategyTest
// Perform a big upload that will stall the flow control windows. // Perform a big upload that will stall the flow control windows.
ByteBuffer data = ByteBuffer.allocate(5 * FlowControlStrategy.DEFAULT_WINDOW_SIZE); ByteBuffer data = ByteBuffer.allocate(5 * FlowControlStrategy.DEFAULT_WINDOW_SIZE);
final CountDownLatch dataLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1);
stream.data(new DataFrame(stream.getId(), data, true), new Callback() stream.data(new DataFrame(stream.getId(), data, true), new Callback()
{ {
@Override @Override

View File

@ -930,9 +930,20 @@ public class HTTP2Test extends AbstractTest
// Avoid aggressive idle timeout to allow the test verifications. // Avoid aggressive idle timeout to allow the test verifications.
connector.setShutdownIdleTimeout(connector.getIdleTimeout()); connector.setShutdownIdleTimeout(connector.getIdleTimeout());
CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClient(new Session.Listener.Adapter() Session clientSession = newClient(new Session.Listener.Adapter()
{ {
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{
if (frame.isGraceful())
clientGracefulGoAwayLatch.countDown();
else
clientGoAwayLatch.countDown();
}
@Override @Override
public void onClose(Session session, GoAwayFrame frame) public void onClose(Session session, GoAwayFrame frame)
{ {
@ -977,26 +988,20 @@ public class HTTP2Test extends AbstractTest
int port = connector.getLocalPort(); int port = connector.getLocalPort();
CompletableFuture<Void> shutdown = Graceful.shutdown(server); CompletableFuture<Void> shutdown = Graceful.shutdown(server);
// GOAWAY should not arrive to the client yet. // Client should receive the graceful GOAWAY.
assertFalse(clientCloseLatch.await(1, TimeUnit.SECONDS)); assertTrue(clientGracefulGoAwayLatch.await(5, TimeUnit.SECONDS));
// Client should not receive the non-graceful GOAWAY.
assertFalse(clientGoAwayLatch.await(500, TimeUnit.MILLISECONDS));
// Client should not be closed yet.
assertFalse(clientCloseLatch.await(500, TimeUnit.MILLISECONDS));
// New requests should be immediately rejected. // Client cannot create new requests after receiving a GOAWAY.
HostPortHttpField authority3 = new HostPortHttpField("localhost" + ":" + port); HostPortHttpField authority3 = new HostPortHttpField("localhost" + ":" + port);
MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP.asString(), authority3, servletPath, HttpVersion.HTTP_2, HttpFields.EMPTY, -1); MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP.asString(), authority3, servletPath, HttpVersion.HTTP_2, HttpFields.EMPTY, -1);
HeadersFrame request3 = new HeadersFrame(metaData3, null, false); HeadersFrame request3 = new HeadersFrame(metaData3, null, true);
FuturePromise<Stream> promise3 = new FuturePromise<>(); FuturePromise<Stream> promise3 = new FuturePromise<>();
CountDownLatch resetLatch = new CountDownLatch(1); clientSession.newStream(request3, promise3, new Stream.Listener.Adapter());
clientSession.newStream(request3, promise3, new Stream.Listener.Adapter() assertThrows(ExecutionException.class, () -> promise3.get(5, TimeUnit.SECONDS));
{
@Override
public void onReset(Stream stream, ResetFrame frame)
{
resetLatch.countDown();
}
});
Stream stream3 = promise3.get(5, TimeUnit.SECONDS);
stream3.data(new DataFrame(stream3.getId(), ByteBuffer.allocate(1), true), Callback.NOOP);
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
// Finish the previous requests and expect the responses. // Finish the previous requests and expect the responses.
stream1.data(new DataFrame(stream1.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP); stream1.data(new DataFrame(stream1.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
@ -1005,9 +1010,9 @@ public class HTTP2Test extends AbstractTest
assertNull(shutdown.get(5, TimeUnit.SECONDS)); assertNull(shutdown.get(5, TimeUnit.SECONDS));
// Now GOAWAY should arrive to the client. // Now GOAWAY should arrive to the client.
assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientCloseLatch.await(5, TimeUnit.SECONDS));
// Wait to process the GOAWAY frames and close the EndPoints.
Thread.sleep(1000);
assertFalse(((HTTP2Session)clientSession).getEndPoint().isOpen()); assertFalse(((HTTP2Session)clientSession).getEndPoint().isOpen());
assertFalse(((HTTP2Session)serverSession).getEndPoint().isOpen()); assertFalse(((HTTP2Session)serverSession).getEndPoint().isOpen());
} }

View File

@ -84,8 +84,8 @@ public class SessionFailureTest extends AbstractTest
@Override @Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{ {
// Forcibly close the connection. // Forcibly shutdown the output to fail the write below.
((HTTP2Session)stream.getSession()).getEndPoint().close(); ((HTTP2Session)stream.getSession()).getEndPoint().shutdownOutput();
// Now try to write something: it should fail. // Now try to write something: it should fail.
stream.headers(frame, new Callback() stream.headers(frame, new Callback()
{ {

View File

@ -321,7 +321,9 @@ public class StreamCloseTest extends AbstractTest
MetaData.Request request = (MetaData.Request)frame.getMetaData(); MetaData.Request request = (MetaData.Request)frame.getMetaData();
if ("GET".equals(request.getMethod())) if ("GET".equals(request.getMethod()))
{ {
((HTTP2Session)stream.getSession()).getEndPoint().close(); // Only shutdown the output, since closing the EndPoint causes a call to
// stop() on different thread which tries to concurrently fail the stream.
((HTTP2Session)stream.getSession()).getEndPoint().shutdownOutput();
// Try to write something to force an error. // Try to write something to force an error.
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1024), true), Callback.NOOP); stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1024), true), Callback.NOOP);
} }

View File

@ -20,319 +20,310 @@ package org.eclipse.jetty.http2;
import java.util.Comparator; import java.util.Comparator;
import org.eclipse.jetty.util.ArrayTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Trie;
public class HTTP2Cipher public class HTTP2Cipher
{ {
public static final Comparator<String> COMPARATOR = new CipherComparator(); public static final Comparator<String> COMPARATOR = new CipherComparator();
private static final Trie<Boolean> __blackProtocols = new ArrayTrie<>(6 * 5); private static final Index<Boolean> __blackProtocols = new Index.Builder<Boolean>()
private static final Trie<Boolean> __blackCiphers = new ArrayTrie<>(275 * 40); .caseSensitive(false)
.with("TLSv1.2", Boolean.TRUE)
.with("TLSv1.1", Boolean.TRUE)
.with("TLSv1", Boolean.TRUE)
.with("SSL", Boolean.TRUE)
.with("SSLv2", Boolean.TRUE)
.with("SSLv3", Boolean.TRUE)
.build();
static private static final Index<Boolean> __blackCiphers = new Index.Builder<Boolean>()
{ .caseSensitive(false)
String[] protocols = {"TLSv1.2", "TLSv1.1", "TLSv1", "SSL", "SSLv2", "SSLv3"}; .with("TLS_NULL_WITH_NULL_NULL", Boolean.TRUE)
for (String p : protocols) .with("TLS_RSA_WITH_NULL_MD5", Boolean.TRUE)
{ .with("TLS_RSA_WITH_NULL_SHA", Boolean.TRUE)
__blackProtocols.put(p, Boolean.TRUE); .with("TLS_RSA_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE)
} .with("TLS_RSA_WITH_RC4_128_MD5", Boolean.TRUE)
.with("TLS_RSA_WITH_RC4_128_SHA", Boolean.TRUE)
String[] ciphers = .with("TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", Boolean.TRUE)
{ .with("TLS_RSA_WITH_IDEA_CBC_SHA", Boolean.TRUE)
"TLS_NULL_WITH_NULL_NULL", .with("TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_NULL_MD5", .with("TLS_RSA_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_NULL_SHA", .with("TLS_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_RSA_EXPORT_WITH_RC4_40_MD5", .with("TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_RC4_128_MD5", .with("TLS_DH_DSS_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_RC4_128_SHA", .with("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", .with("TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_IDEA_CBC_SHA", .with("TLS_DH_RSA_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", .with("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_DES_CBC_SHA", .with("TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_DHE_DSS_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", .with("TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_DES_CBC_SHA", .with("TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", .with("TLS_DHE_RSA_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", .with("TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_WITH_DES_CBC_SHA", .with("TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE)
"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_DH_anon_WITH_RC4_128_MD5", Boolean.TRUE)
"TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", .with("TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", Boolean.TRUE)
"TLS_DHE_DSS_WITH_DES_CBC_SHA", .with("TLS_DH_anon_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", .with("TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", .with("TLS_KRB5_WITH_DES_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_DES_CBC_SHA", .with("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_KRB5_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", .with("TLS_KRB5_WITH_IDEA_CBC_SHA", Boolean.TRUE)
"TLS_DH_anon_WITH_RC4_128_MD5", .with("TLS_KRB5_WITH_DES_CBC_MD5", Boolean.TRUE)
"TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", .with("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", Boolean.TRUE)
"TLS_DH_anon_WITH_DES_CBC_SHA", .with("TLS_KRB5_WITH_RC4_128_MD5", Boolean.TRUE)
"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", .with("TLS_KRB5_WITH_IDEA_CBC_MD5", Boolean.TRUE)
"TLS_KRB5_WITH_DES_CBC_SHA", .with("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", Boolean.TRUE)
"TLS_KRB5_WITH_3DES_EDE_CBC_SHA", .with("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", Boolean.TRUE)
"TLS_KRB5_WITH_RC4_128_SHA", .with("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", Boolean.TRUE)
"TLS_KRB5_WITH_IDEA_CBC_SHA", .with("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", Boolean.TRUE)
"TLS_KRB5_WITH_DES_CBC_MD5", .with("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", Boolean.TRUE)
"TLS_KRB5_WITH_3DES_EDE_CBC_MD5", .with("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", Boolean.TRUE)
"TLS_KRB5_WITH_RC4_128_MD5", .with("TLS_PSK_WITH_NULL_SHA", Boolean.TRUE)
"TLS_KRB5_WITH_IDEA_CBC_MD5", .with("TLS_DHE_PSK_WITH_NULL_SHA", Boolean.TRUE)
"TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", .with("TLS_RSA_PSK_WITH_NULL_SHA", Boolean.TRUE)
"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", .with("TLS_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_KRB5_EXPORT_WITH_RC4_40_SHA", .with("TLS_DH_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", .with("TLS_DH_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", .with("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_KRB5_EXPORT_WITH_RC4_40_MD5", .with("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_PSK_WITH_NULL_SHA", .with("TLS_DH_anon_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_DHE_PSK_WITH_NULL_SHA", .with("TLS_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_RSA_PSK_WITH_NULL_SHA", .with("TLS_DH_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_AES_128_CBC_SHA", .with("TLS_DH_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_AES_128_CBC_SHA", .with("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_WITH_AES_128_CBC_SHA", .with("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", .with("TLS_DH_anon_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", .with("TLS_RSA_WITH_NULL_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_AES_128_CBC_SHA", .with("TLS_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_AES_256_CBC_SHA", .with("TLS_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_AES_256_CBC_SHA", .with("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_AES_256_CBC_SHA", .with("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", .with("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", .with("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
"TLS_DH_anon_WITH_AES_256_CBC_SHA", .with("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_NULL_SHA256", .with("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_AES_128_CBC_SHA256", .with("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_AES_256_CBC_SHA256", .with("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_AES_128_CBC_SHA256", .with("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_WITH_AES_128_CBC_SHA256", .with("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", .with("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", .with("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", .with("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", .with("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", .with("TLS_DH_anon_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", .with("TLS_DH_anon_WITH_AES_256_CBC_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", .with("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", .with("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_AES_256_CBC_SHA256", .with("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_WITH_AES_256_CBC_SHA256", .with("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", .with("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", .with("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", Boolean.TRUE)
"TLS_DH_anon_WITH_AES_128_CBC_SHA256", .with("TLS_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_DH_anon_WITH_AES_256_CBC_SHA256", .with("TLS_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", .with("TLS_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", .with("TLS_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", .with("TLS_DHE_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", .with("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", .with("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", .with("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_PSK_WITH_RC4_128_SHA", .with("TLS_RSA_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_PSK_WITH_3DES_EDE_CBC_SHA", .with("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_PSK_WITH_AES_128_CBC_SHA", .with("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_PSK_WITH_AES_256_CBC_SHA", .with("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_PSK_WITH_RC4_128_SHA", .with("TLS_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE)
"TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", .with("TLS_DH_DSS_WITH_SEED_CBC_SHA", Boolean.TRUE)
"TLS_DHE_PSK_WITH_AES_128_CBC_SHA", .with("TLS_DH_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE)
"TLS_DHE_PSK_WITH_AES_256_CBC_SHA", .with("TLS_DHE_DSS_WITH_SEED_CBC_SHA", Boolean.TRUE)
"TLS_RSA_PSK_WITH_RC4_128_SHA", .with("TLS_DHE_RSA_WITH_SEED_CBC_SHA", Boolean.TRUE)
"TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", .with("TLS_DH_anon_WITH_SEED_CBC_SHA", Boolean.TRUE)
"TLS_RSA_PSK_WITH_AES_128_CBC_SHA", .with("TLS_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_AES_256_CBC_SHA", .with("TLS_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_RSA_WITH_SEED_CBC_SHA", .with("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_SEED_CBC_SHA", .with("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_RSA_WITH_SEED_CBC_SHA", .with("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_DHE_DSS_WITH_SEED_CBC_SHA", .with("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_DHE_RSA_WITH_SEED_CBC_SHA", .with("TLS_DH_anon_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_SEED_CBC_SHA", .with("TLS_DH_anon_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_RSA_WITH_AES_128_GCM_SHA256", .with("TLS_PSK_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_AES_256_GCM_SHA384", .with("TLS_PSK_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_RSA_WITH_AES_128_GCM_SHA256", .with("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_AES_256_GCM_SHA384", .with("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_DSS_WITH_AES_128_GCM_SHA256", .with("TLS_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_AES_256_GCM_SHA384", .with("TLS_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_DH_anon_WITH_AES_128_GCM_SHA256", .with("TLS_PSK_WITH_NULL_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_AES_256_GCM_SHA384", .with("TLS_PSK_WITH_NULL_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_AES_128_GCM_SHA256", .with("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_PSK_WITH_AES_256_GCM_SHA384", .with("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", .with("TLS_DHE_PSK_WITH_NULL_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", .with("TLS_DHE_PSK_WITH_NULL_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_AES_128_CBC_SHA256", .with("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_PSK_WITH_AES_256_CBC_SHA384", .with("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_NULL_SHA256", .with("TLS_RSA_PSK_WITH_NULL_SHA256", Boolean.TRUE)
"TLS_PSK_WITH_NULL_SHA384", .with("TLS_RSA_PSK_WITH_NULL_SHA384", Boolean.TRUE)
"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", .with("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", .with("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_PSK_WITH_NULL_SHA256", .with("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_PSK_WITH_NULL_SHA384", .with("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", .with("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", .with("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_NULL_SHA256", .with("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_NULL_SHA384", .with("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", Boolean.TRUE)
"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_NULL_SHA", Boolean.TRUE)
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", .with("TLS_ECDHE_ECDSA_WITH_NULL_SHA", Boolean.TRUE)
"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", .with("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV", .with("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_NULL_SHA", .with("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA", .with("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_ECDH_RSA_WITH_NULL_SHA", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", .with("TLS_ECDH_RSA_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", .with("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_NULL_SHA", .with("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", .with("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_ECDHE_RSA_WITH_NULL_SHA", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", .with("TLS_ECDHE_RSA_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", .with("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_NULL_SHA", .with("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_RC4_128_SHA", .with("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_ECDH_anon_WITH_NULL_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", .with("TLS_ECDH_anon_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", .with("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_NULL_SHA", .with("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_RC4_128_SHA", .with("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", .with("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", .with("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_anon_WITH_NULL_SHA", .with("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_anon_WITH_RC4_128_SHA", .with("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", .with("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_anon_WITH_AES_128_CBC_SHA", .with("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_anon_WITH_AES_256_CBC_SHA", .with("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", .with("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", .with("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", .with("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_SRP_SHA_WITH_AES_128_CBC_SHA", .with("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", .with("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", .with("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_SRP_SHA_WITH_AES_256_CBC_SHA", .with("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", .with("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", .with("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", .with("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", .with("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", .with("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", .with("TLS_ECDHE_PSK_WITH_RC4_128_SHA", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", .with("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", .with("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", .with("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", .with("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", .with("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", .with("TLS_ECDHE_PSK_WITH_NULL_SHA", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", .with("TLS_ECDHE_PSK_WITH_NULL_SHA256", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_RC4_128_SHA", .with("TLS_ECDHE_PSK_WITH_NULL_SHA384", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", .with("TLS_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", .with("TLS_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", .with("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", .with("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", .with("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_NULL_SHA", .with("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_NULL_SHA256", .with("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_NULL_SHA384", .with("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_RSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", .with("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", .with("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", .with("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", .with("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_RSA_WITH_ARIA_128_GCM_SHA256", .with("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_ARIA_256_GCM_SHA384", .with("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", .with("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", .with("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", .with("TLS_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", .with("TLS_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", .with("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", .with("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", .with("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", .with("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", .with("TLS_PSK_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", .with("TLS_PSK_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_ARIA_128_CBC_SHA256", .with("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_PSK_WITH_ARIA_256_CBC_SHA384", .with("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_ARIA_128_GCM_SHA256", .with("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_PSK_WITH_ARIA_256_GCM_SHA384", .with("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", .with("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", .with("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", .with("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", .with("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", Boolean.TRUE)
"TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", .with("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", Boolean.TRUE)
"TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", .with("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", Boolean.TRUE)
"TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_RSA_WITH_AES_128_CCM", Boolean.TRUE)
"TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_RSA_WITH_AES_256_CCM", Boolean.TRUE)
"TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_RSA_WITH_AES_128_CCM_8", Boolean.TRUE)
"TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_RSA_WITH_AES_256_CCM_8", Boolean.TRUE)
"TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_PSK_WITH_AES_128_CCM", Boolean.TRUE)
"TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_PSK_WITH_AES_256_CCM", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", .with("TLS_PSK_WITH_AES_128_CCM_8", Boolean.TRUE)
"TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", .with("TLS_PSK_WITH_AES_256_CCM_8", Boolean.TRUE)
"TLS_RSA_WITH_AES_128_CCM", .build();
"TLS_RSA_WITH_AES_256_CCM",
"TLS_RSA_WITH_AES_128_CCM_8",
"TLS_RSA_WITH_AES_256_CCM_8",
"TLS_PSK_WITH_AES_128_CCM",
"TLS_PSK_WITH_AES_256_CCM",
"TLS_PSK_WITH_AES_128_CCM_8",
"TLS_PSK_WITH_AES_256_CCM_8"
};
for (String c : ciphers)
{
__blackCiphers.put(c, Boolean.TRUE);
}
}
public static boolean isBlackListProtocol(String tlsProtocol) public static boolean isBlackListProtocol(String tlsProtocol)
{ {
Boolean b = __blackProtocols.get(tlsProtocol); return __blackProtocols.get(tlsProtocol) != null;
return b != null && b;
} }
public static boolean isBlackListCipher(String tlsCipher) public static boolean isBlackListCipher(String tlsCipher)
{ {
Boolean b = __blackCiphers.get(tlsCipher); return __blackCiphers.get(tlsCipher) != null;
return b != null && b;
} }
/** /**

View File

@ -267,7 +267,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
{ {
Runnable task = pollTask(); Runnable task = pollTask();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Dequeued task {}", task); LOG.debug("Dequeued task {}", String.valueOf(task));
if (task != null) if (task != null)
return task; return task;

View File

@ -367,7 +367,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
// If the failure came from within the // If the failure came from within the
// flusher, we need to close the connection. // flusher, we need to close the connection.
if (closed == null) if (closed == null)
session.abort(x); session.onWriteFailure(x);
} }
void terminate(Throwable cause) void terminate(Throwable cause)
@ -378,7 +378,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
closed = terminated; closed = terminated;
terminated = cause; terminated = cause;
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("{}", closed != null ? "Terminated" : "Terminating"); LOG.debug("{} {}", closed != null ? "Terminated" : "Terminating", this);
} }
if (closed == null) if (closed == null)
iterate(); iterate();

View File

@ -158,7 +158,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
localReset = true; localReset = true;
failure = new EOFException("reset"); failure = new EOFException("reset");
} }
session.frames(this, List.of(frame), callback); ((HTTP2Session)session).reset(this, frame, callback);
} }
private boolean startWrite(Callback callback) private boolean startWrite(Callback callback)
@ -367,24 +367,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
length = fields.getLongField(HttpHeader.CONTENT_LENGTH); length = fields.getLongField(HttpHeader.CONTENT_LENGTH);
dataLength = length >= 0 ? length : Long.MIN_VALUE; dataLength = length >= 0 ? length : Long.MIN_VALUE;
} }
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
callback.succeeded(); callback.succeeded();
} }
private void onData(DataFrame frame, Callback callback) private void onData(DataFrame frame, Callback callback)
{ {
if (getRecvWindow() < 0)
{
// It's a bad client, it does not deserve to be
// treated gently by just resetting the stream.
((HTTP2Session)session).onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded");
callback.failed(new IOException("stream_window_exceeded"));
return;
}
// SPEC: remotely closed streams must be replied with a reset. // SPEC: remotely closed streams must be replied with a reset.
if (isRemotelyClosed()) if (isRemotelyClosed())
{ {
@ -483,9 +470,10 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
dataEntry = dataQueue.poll(); dataEntry = dataQueue.poll();
} }
DataFrame frame = dataEntry.frame; DataFrame frame = dataEntry.frame;
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED)) boolean closed = updateClose(frame.isEndStream(), CloseState.Event.RECEIVED);
session.removeStream(this);
notifyDataDemanded(this, frame, dataEntry.callback); notifyDataDemanded(this, frame, dataEntry.callback);
if (closed)
session.removeStream(this);
} }
} }
@ -505,8 +493,8 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
failure = new EofException("reset"); failure = new EofException("reset");
} }
close(); close();
session.removeStream(this); if (session.removeStream(this))
notifyReset(this, frame, callback); notifyReset(this, frame, callback);
} }
private void onPush(PushPromiseFrame frame, Callback callback) private void onPush(PushPromiseFrame frame, Callback callback)
@ -529,8 +517,8 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
failure = frame.getFailure(); failure = frame.getFailure();
} }
close(); close();
session.removeStream(this); if (session.removeStream(this))
notifyFailure(this, frame, callback); notifyFailure(this, frame, callback);
} }
@Override @Override

View File

@ -45,8 +45,9 @@ public interface ISession extends Session
* <p>Removes the given {@code stream}.</p> * <p>Removes the given {@code stream}.</p>
* *
* @param stream the stream to remove * @param stream the stream to remove
* @return whether the stream was removed
*/ */
public void removeStream(IStream stream); public boolean removeStream(IStream stream);
/** /**
* <p>Sends the given list of frames to create a new {@link Stream}.</p> * <p>Sends the given list of frames to create a new {@link Stream}.</p>

View File

@ -113,8 +113,6 @@ public interface Session
/** /**
* <p>Closes the session by sending a GOAWAY frame with the given error code * <p>Closes the session by sending a GOAWAY frame with the given error code
* and payload.</p> * and payload.</p>
* <p>The GOAWAY frame is sent only once; subsequent or concurrent attempts to
* close the session will have no effect.</p>
* *
* @param error the error code * @param error the error code
* @param payload an optional payload (may be null) * @param payload an optional payload (may be null)
@ -225,6 +223,16 @@ public interface Session
* *
* @param session the session * @param session the session
* @param frame the GOAWAY frame received * @param frame the GOAWAY frame received
*/
default void onGoAway(Session session, GoAwayFrame frame)
{
}
/**
* <p>Callback method invoked when a GOAWAY frame caused the session to be closed.</p>
*
* @param session the session
* @param frame the GOAWAY frame that caused the session to be closed
* @param callback the callback to notify of the GOAWAY processing * @param callback the callback to notify of the GOAWAY processing
*/ */
public default void onClose(Session session, GoAwayFrame frame, Callback callback) public default void onClose(Session session, GoAwayFrame frame, Callback callback)

View File

@ -20,30 +20,33 @@ package org.eclipse.jetty.http2.frames;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.ErrorCode;
public class GoAwayFrame extends Frame public class GoAwayFrame extends Frame
{ {
private final CloseState closeState; public static final GoAwayFrame GRACEFUL = new GoAwayFrame(Integer.MAX_VALUE, ErrorCode.NO_ERROR.code, new byte[]{'g', 'r', 'a', 'c', 'e', 'f', 'u', 'l'});
private final int lastStreamId; private final int lastStreamId;
private final int error; private final int error;
private final byte[] payload; private final byte[] payload;
public GoAwayFrame(int lastStreamId, int error, byte[] payload) public GoAwayFrame(int lastStreamId, int error, byte[] payload)
{
this(CloseState.REMOTELY_CLOSED, lastStreamId, error, payload);
}
public GoAwayFrame(CloseState closeState, int lastStreamId, int error, byte[] payload)
{ {
super(FrameType.GO_AWAY); super(FrameType.GO_AWAY);
this.closeState = closeState;
this.lastStreamId = lastStreamId; this.lastStreamId = lastStreamId;
this.error = error; this.error = error;
this.payload = payload; this.payload = payload;
} }
/**
* @return whether this GOAWAY frame is graceful, i.e. its {@code lastStreamId == Integer.MAX_VALUE}
*/
public boolean isGraceful()
{
// SPEC: section 6.8.
return lastStreamId == Integer.MAX_VALUE;
}
public int getLastStreamId() public int getLastStreamId()
{ {
return lastStreamId; return lastStreamId;
@ -76,11 +79,10 @@ public class GoAwayFrame extends Frame
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s,%d/%s/%s/%s", return String.format("%s{%d/%s/%s}",
super.toString(), super.toString(),
lastStreamId, lastStreamId,
ErrorCode.toString(error, null), ErrorCode.toString(error, null),
tryConvertPayload(), tryConvertPayload());
closeState);
} }
} }

View File

@ -29,9 +29,8 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -114,13 +113,14 @@ public class HpackContext
}; };
private static final Map<HttpField, Entry> __staticFieldMap = new HashMap<>(); private static final Map<HttpField, Entry> __staticFieldMap = new HashMap<>();
private static final Trie<StaticEntry> __staticNameMap = new ArrayTernaryTrie<>(true, 512); private static final Index<StaticEntry> __staticNameMap;
private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.UNKNOWN.ordinal()]; private static final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.values().length];
private static final StaticEntry[] __staticTable = new StaticEntry[STATIC_TABLE.length]; private static final StaticEntry[] __staticTable = new StaticEntry[STATIC_TABLE.length];
public static final int STATIC_SIZE = STATIC_TABLE.length - 1; public static final int STATIC_SIZE = STATIC_TABLE.length - 1;
static static
{ {
Index.Builder<StaticEntry> staticNameMapBuilder = new Index.Builder<StaticEntry>().caseSensitive(false);
Set<String> added = new HashSet<>(); Set<String> added = new HashSet<>();
for (int i = 1; i < STATIC_TABLE.length; i++) for (int i = 1; i < STATIC_TABLE.length; i++)
{ {
@ -173,11 +173,10 @@ public class HpackContext
if (!added.contains(entry._field.getName())) if (!added.contains(entry._field.getName()))
{ {
added.add(entry._field.getName()); added.add(entry._field.getName());
__staticNameMap.put(entry._field.getName(), entry); staticNameMapBuilder.with(entry._field.getName(), entry);
if (__staticNameMap.get(entry._field.getName()) == null)
throw new IllegalStateException("name trie too small");
} }
} }
__staticNameMap = staticNameMapBuilder.build();
for (HttpHeader h : HttpHeader.values()) for (HttpHeader h : HttpHeader.values())
{ {

View File

@ -40,13 +40,13 @@ import org.eclipse.jetty.client.SendFailure;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.Sweeper; import org.eclipse.jetty.util.thread.Sweeper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -99,29 +99,43 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
public void upgrade(Map<String, Object> context) public void upgrade(Map<String, Object> context)
{ {
HttpResponse response = (HttpResponse)context.get(HttpResponse.class.getName());
HttpRequest request = (HttpRequest)response.getRequest();
// In case of HTTP/1.1 upgrade to HTTP/2, the request is HTTP/1.1 // In case of HTTP/1.1 upgrade to HTTP/2, the request is HTTP/1.1
// (with upgrade) for a resource, and the response is HTTP/2. // (with upgrade) for a resource, and the response is HTTP/2.
// Create the implicit stream#1 so that it can receive the HTTP/2 response.
MetaData.Request metaData = new MetaData.Request(request.getMethod(), HttpURI.from(request.getURI()), HttpVersion.HTTP_2, request.getHeaders()); HttpResponse response = (HttpResponse)context.get(HttpResponse.class.getName());
// We do not support upgrade requests with content, so endStream=true. HttpRequest request = (HttpRequest)response.getRequest();
HeadersFrame frame = new HeadersFrame(metaData, null, true);
IStream stream = ((HTTP2Session)session).newLocalStream(frame, null);
stream.updateClose(frame.isEndStream(), CloseState.Event.AFTER_SEND);
HttpExchange exchange = request.getConversation().getExchanges().peekLast(); HttpExchange exchange = request.getConversation().getExchanges().peekLast();
HttpChannelOverHTTP2 http2Channel = acquireHttpChannel(); HttpChannelOverHTTP2 http2Channel = acquireHttpChannel();
activeChannels.add(http2Channel); activeChannels.add(http2Channel);
HttpExchange newExchange = new HttpExchange(exchange.getHttpDestination(), exchange.getRequest(), List.of()); HttpExchange newExchange = new HttpExchange(exchange.getHttpDestination(), exchange.getRequest(), List.of());
http2Channel.associate(newExchange); http2Channel.associate(newExchange);
stream.setListener(http2Channel.getStreamListener());
http2Channel.setStream(stream);
newExchange.requestComplete(null);
newExchange.terminateRequest();
if (LOG.isDebugEnabled()) // Create the implicit stream#1 so that it can receive the HTTP/2 response.
LOG.debug("Upgrade completed for {}", this); MetaData.Request metaData = new MetaData.Request(request.getMethod(), HttpURI.from(request.getURI()), HttpVersion.HTTP_2, request.getHeaders());
// We do not support upgrade requests with content, so endStream=true.
HeadersFrame frame = new HeadersFrame(metaData, null, true);
((HTTP2Session)session).newUpgradeStream(frame, http2Channel.getStreamListener(), new Promise<>()
{
@Override
public void succeeded(Stream stream)
{
http2Channel.setStream(stream);
newExchange.requestComplete(null);
newExchange.terminateRequest();
if (LOG.isDebugEnabled())
LOG.debug("Upgrade succeeded for {}", HttpConnectionOverHTTP2.this);
}
@Override
public void failed(Throwable failure)
{
newExchange.requestComplete(failure);
newExchange.terminateRequest();
if (LOG.isDebugEnabled())
LOG.debug("Upgrade failed for {}", HttpConnectionOverHTTP2.this);
}
});
} }
@Override @Override

View File

@ -35,6 +35,7 @@ public class AbstractTest
{ {
protected Server server; protected Server server;
protected ServerConnector connector; protected ServerConnector connector;
protected HTTP2Client http2Client;
protected HttpClient client; protected HttpClient client;
protected void start(ServerSessionListener listener) throws Exception protected void start(ServerSessionListener listener) throws Exception
@ -63,12 +64,13 @@ public class AbstractTest
server.addConnector(connector); server.addConnector(connector);
} }
protected void prepareClient() throws Exception protected void prepareClient()
{ {
client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client())); http2Client = new HTTP2Client();
client = new HttpClient(new HttpClientTransportOverHTTP2(http2Client));
QueuedThreadPool clientExecutor = new QueuedThreadPool(); QueuedThreadPool clientExecutor = new QueuedThreadPool();
clientExecutor.setName("client"); clientExecutor.setName("client");
client.setExecutor(clientExecutor); this.client.setExecutor(clientExecutor);
} }
@AfterEach @AfterEach

View File

@ -226,7 +226,9 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
MetaData.Request request = (MetaData.Request)frame.getMetaData(); MetaData.Request request = (MetaData.Request)frame.getMetaData();
if (HttpMethod.HEAD.is(request.getMethod())) if (HttpMethod.HEAD.is(request.getMethod()))
{ {
stream.getSession().close(ErrorCode.REFUSED_STREAM_ERROR.code, null, Callback.NOOP); int error = ErrorCode.REFUSED_STREAM_ERROR.code;
stream.reset(new ResetFrame(stream.getId(), error), Callback.NOOP);
stream.getSession().close(error, null, Callback.NOOP);
} }
else else
{ {

View File

@ -1,6 +1,6 @@
# Jetty Logging using jetty-slf4j-impl # Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG #org.eclipse.jetty.client.LEVEL=DEBUG
org.eclipse.jetty.http2.hpack.LEVEL=INFO
#org.eclipse.jetty.http2.LEVEL=DEBUG #org.eclipse.jetty.http2.LEVEL=DEBUG
org.eclipse.jetty.http2.hpack.LEVEL=INFO
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG #org.eclipse.jetty.io.ssl.LEVEL=DEBUG

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.HTTP2Session;
@ -65,18 +66,12 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
settings = Collections.emptyMap(); settings = Collections.emptyMap();
SettingsFrame settingsFrame = new SettingsFrame(settings, false); SettingsFrame settingsFrame = new SettingsFrame(settings, false);
WindowUpdateFrame windowFrame = null;
int sessionWindow = getInitialSessionRecvWindow() - FlowControlStrategy.DEFAULT_WINDOW_SIZE; int sessionWindow = getInitialSessionRecvWindow() - FlowControlStrategy.DEFAULT_WINDOW_SIZE;
updateRecvWindow(sessionWindow);
if (sessionWindow > 0) if (sessionWindow > 0)
{ frames(null, List.of(settingsFrame, new WindowUpdateFrame(0, sessionWindow)), Callback.NOOP);
updateRecvWindow(sessionWindow);
windowFrame = new WindowUpdateFrame(0, sessionWindow);
}
if (windowFrame == null)
frames(null, List.of(settingsFrame), Callback.NOOP);
else else
frames(null, List.of(settingsFrame, windowFrame), Callback.NOOP); frames(null, List.of(settingsFrame), Callback.NOOP);
} }
@Override @Override
@ -105,31 +100,26 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
} }
else else
{ {
if (isClosed()) stream = createRemoteStream(streamId, (MetaData.Request)metaData);
if (stream != null)
{ {
updateLastRemoteStreamId(streamId); onStreamOpened(stream);
reset(new ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
} if (metaData instanceof MetaData.ConnectRequest)
else
{
stream = createRemoteStream(streamId, (MetaData.Request)metaData);
if (stream != null)
{ {
onStreamOpened(stream); if (!isConnectProtocolEnabled() && ((MetaData.ConnectRequest)metaData).getProtocol() != null)
if (metaData instanceof MetaData.ConnectRequest)
{ {
if (!isConnectProtocolEnabled() && ((MetaData.ConnectRequest)metaData).getProtocol() != null) stream.reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
{ return;
stream.reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
return;
}
} }
stream.process(frame, Callback.NOOP);
Stream.Listener listener = notifyNewStream(stream, frame);
stream.setListener(listener);
} }
stream.process(frame, Callback.NOOP);
boolean closed = stream.updateClose(frame.isEndStream(), CloseState.Event.RECEIVED);
Stream.Listener listener = notifyNewStream(stream, frame);
stream.setListener(listener);
if (closed)
removeStream(stream);
} }
} }
} }
@ -148,7 +138,10 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
if (stream != null) if (stream != null)
{ {
stream.process(frame, Callback.NOOP); stream.process(frame, Callback.NOOP);
boolean closed = stream.updateClose(frame.isEndStream(), CloseState.Event.RECEIVED);
notifyHeaders(stream, frame); notifyHeaders(stream, frame);
if (closed)
removeStream(stream);
} }
else else
{ {

View File

@ -109,6 +109,12 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti
delegate.onReset(session, frame); delegate.onReset(session, frame);
} }
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{
delegate.onGoAway(session, frame);
}
@Override @Override
public void onClose(Session session, GoAwayFrame frame) public void onClose(Session session, GoAwayFrame frame)
{ {

View File

@ -28,8 +28,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Trie;
/** /**
* Special handling for MSIE (Microsoft Internet Explorer). * Special handling for MSIE (Microsoft Internet Explorer).
@ -42,21 +41,19 @@ public class MsieRule extends Rule
{ {
private static final int IEv5 = '5'; private static final int IEv5 = '5';
private static final int IEv6 = '6'; private static final int IEv6 = '6';
private static final Trie<Boolean> __IE6_BadOS = new ArrayTernaryTrie<>(); private static final Index<Boolean> __IE6_BadOS = new Index.Builder<Boolean>()
.caseSensitive(false)
.with("NT 5.01", Boolean.TRUE)
.with("NT 5.0", Boolean.TRUE)
.with("NT 4.0", Boolean.TRUE)
.with("98", Boolean.TRUE)
.with("98; Win 9x 4.90", Boolean.TRUE)
.with("95", Boolean.TRUE)
.with("CE", Boolean.TRUE)
.build();
private static final HttpField CONNECTION_CLOSE = new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); private static final HttpField CONNECTION_CLOSE = new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
private static final HttpField VARY_USER_AGENT = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.USER_AGENT.asString()); private static final HttpField VARY_USER_AGENT = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.USER_AGENT.asString());
static
{
__IE6_BadOS.put("NT 5.01", Boolean.TRUE);
__IE6_BadOS.put("NT 5.0", Boolean.TRUE);
__IE6_BadOS.put("NT 4.0", Boolean.TRUE);
__IE6_BadOS.put("98", Boolean.TRUE);
__IE6_BadOS.put("98; Win 9x 4.90", Boolean.TRUE);
__IE6_BadOS.put("95", Boolean.TRUE);
__IE6_BadOS.put("CE", Boolean.TRUE);
}
public MsieRule() public MsieRule()
{ {
_handling = false; _handling = false;

View File

@ -24,8 +24,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Trie;
/** /**
* MSIE (Microsoft Internet Explorer) SSL Rule. * MSIE (Microsoft Internet Explorer) SSL Rule.
@ -37,17 +36,16 @@ public class MsieSslRule extends Rule
{ {
private static final int IEv5 = '5'; private static final int IEv5 = '5';
private static final int IEv6 = '6'; private static final int IEv6 = '6';
private static Trie<Boolean> __IE6_BadOS = new ArrayTernaryTrie<>(); private static final Index<Boolean> __IE6_BadOS = new Index.Builder<Boolean>()
.caseSensitive(false)
{ .with("NT 5.01", Boolean.TRUE)
__IE6_BadOS.put("NT 5.01", Boolean.TRUE); .with("NT 5.0", Boolean.TRUE)
__IE6_BadOS.put("NT 5.0", Boolean.TRUE); .with("NT 4.0", Boolean.TRUE)
__IE6_BadOS.put("NT 4.0", Boolean.TRUE); .with("98", Boolean.TRUE)
__IE6_BadOS.put("98", Boolean.TRUE); .with("98; Win 9x 4.90", Boolean.TRUE)
__IE6_BadOS.put("98; Win 9x 4.90", Boolean.TRUE); .with("95", Boolean.TRUE)
__IE6_BadOS.put("95", Boolean.TRUE); .with("CE", Boolean.TRUE)
__IE6_BadOS.put("CE", Boolean.TRUE); .build();
}
public MsieSslRule() public MsieSslRule()
{ {

View File

@ -33,10 +33,9 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.QuotedCSVParser; import org.eclipse.jetty.http.QuotedCSVParser;
import org.eclipse.jetty.server.HttpConfiguration.Customizer; import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import static java.lang.invoke.MethodType.methodType; import static java.lang.invoke.MethodType.methodType;
@ -159,7 +158,10 @@ public class ForwardedRequestCustomizer implements Customizer
private String _forwardedCipherSuiteHeader = "Proxy-auth-cert"; private String _forwardedCipherSuiteHeader = "Proxy-auth-cert";
private String _forwardedSslSessionIdHeader = "Proxy-ssl-id"; private String _forwardedSslSessionIdHeader = "Proxy-ssl-id";
private boolean _sslIsSecure = true; private boolean _sslIsSecure = true;
private Trie<MethodHandle> _handles; private final Index.Mutable<MethodHandle> _handles = new Index.Builder<MethodHandle>()
.caseSensitive(false)
.mutable()
.build();
public ForwardedRequestCustomizer() public ForwardedRequestCustomizer()
{ {
@ -596,52 +598,33 @@ public class ForwardedRequestCustomizer implements Customizer
private void updateHandles() private void updateHandles()
{ {
int size = 0;
MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandles.Lookup lookup = MethodHandles.lookup();
try
// Loop to grow capacity of ArrayTrie for all headers
while (true)
{ {
try updateForwardedHandle(lookup, getForwardedHeader(), "handleRFC7239");
{ updateForwardedHandle(lookup, getForwardedHostHeader(), "handleForwardedHost");
size += 128; // experimented good baseline size updateForwardedHandle(lookup, getForwardedForHeader(), "handleForwardedFor");
_handles = new ArrayTrie<>(size); updateForwardedHandle(lookup, getForwardedPortHeader(), "handleForwardedPort");
updateForwardedHandle(lookup, getForwardedProtoHeader(), "handleProto");
if (updateForwardedHandle(lookup, getForwardedHeader(), "handleRFC7239")) updateForwardedHandle(lookup, getForwardedHttpsHeader(), "handleHttps");
continue; updateForwardedHandle(lookup, getForwardedServerHeader(), "handleForwardedServer");
if (updateForwardedHandle(lookup, getForwardedHostHeader(), "handleForwardedHost")) updateForwardedHandle(lookup, getForwardedCipherSuiteHeader(), "handleCipherSuite");
continue; updateForwardedHandle(lookup, getForwardedSslSessionIdHeader(), "handleSslSessionId");
if (updateForwardedHandle(lookup, getForwardedForHeader(), "handleForwardedFor")) }
continue; catch (NoSuchMethodException | IllegalAccessException e)
if (updateForwardedHandle(lookup, getForwardedPortHeader(), "handleForwardedPort")) {
continue; throw new IllegalStateException(e);
if (updateForwardedHandle(lookup, getForwardedProtoHeader(), "handleProto"))
continue;
if (updateForwardedHandle(lookup, getForwardedHttpsHeader(), "handleHttps"))
continue;
if (updateForwardedHandle(lookup, getForwardedServerHeader(), "handleForwardedServer"))
continue;
if (updateForwardedHandle(lookup, getForwardedCipherSuiteHeader(), "handleCipherSuite"))
continue;
if (updateForwardedHandle(lookup, getForwardedSslSessionIdHeader(), "handleSslSessionId"))
continue;
break;
}
catch (NoSuchMethodException | IllegalAccessException e)
{
throw new IllegalStateException(e);
}
} }
} }
private boolean updateForwardedHandle(MethodHandles.Lookup lookup, String headerName, String forwardedMethodName) throws NoSuchMethodException, IllegalAccessException private void updateForwardedHandle(MethodHandles.Lookup lookup, String headerName, String forwardedMethodName) throws NoSuchMethodException, IllegalAccessException
{ {
final MethodType type = methodType(void.class, HttpField.class); final MethodType type = methodType(void.class, HttpField.class);
if (StringUtil.isBlank(headerName)) if (StringUtil.isBlank(headerName))
return false; return;
return !_handles.put(headerName, lookup.findVirtual(Forwarded.class, forwardedMethodName, type)); _handles.put(headerName, lookup.findVirtual(Forwarded.class, forwardedMethodName, type));
} }
private static class MutableHostPort private static class MutableHostPort

View File

@ -508,38 +508,27 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
if (HttpVersion.HTTP_1_1.equals(_requestBuilder.version())) if (HttpVersion.HTTP_1_1.equals(_requestBuilder.version()))
{ {
HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value); HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
switch (expect == null ? HttpHeaderValue.UNKNOWN : expect) if (expect == HttpHeaderValue.CONTINUE)
{ {
case CONTINUE: _expect100Continue = true;
_expect100Continue = true; }
break; else if (expect == HttpHeaderValue.PROCESSING)
{
case PROCESSING: _expect102Processing = true;
_expect102Processing = true; }
break; else
{
default: String[] values = field.getValues();
String[] values = field.getValues(); for (int i = 0; values != null && i < values.length; i++)
for (int i = 0; values != null && i < values.length; i++) {
{ expect = HttpHeaderValue.CACHE.get(values[i].trim());
expect = HttpHeaderValue.CACHE.get(values[i].trim()); if (expect == HttpHeaderValue.CONTINUE)
if (expect == null) _expect100Continue = true;
_unknownExpectation = true; else if (expect == HttpHeaderValue.PROCESSING)
else _expect102Processing = true;
{ else
switch (expect) _unknownExpectation = true;
{ }
case CONTINUE:
_expect100Continue = true;
break;
case PROCESSING:
_expect102Processing = true;
break;
default:
_unknownExpectation = true;
}
}
}
} }
} }
break; break;

View File

@ -27,9 +27,8 @@ import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.TreeTrie;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
@ -51,7 +50,10 @@ public class HttpConfiguration implements Dumpable
{ {
public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")"; public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
private final List<Customizer> _customizers = new CopyOnWriteArrayList<>(); private final List<Customizer> _customizers = new CopyOnWriteArrayList<>();
private final Trie<Boolean> _formEncodedMethods = new TreeTrie<>(); private final Index.Mutable<Boolean> _formEncodedMethods = new Index.Builder<Boolean>()
.caseSensitive(false)
.mutable()
.build();
private int _outputBufferSize = 32 * 1024; private int _outputBufferSize = 32 * 1024;
private int _outputAggregationSize = _outputBufferSize / 4; private int _outputAggregationSize = _outputBufferSize / 4;
private int _requestHeaderSize = 8 * 1024; private int _requestHeaderSize = 8 * 1024;
@ -424,7 +426,7 @@ public class HttpConfiguration implements Dumpable
/** /**
* @param headerCacheSize The size of the header field cache, in terms of unique characters branches * @param headerCacheSize The size of the header field cache, in terms of unique characters branches
* in the lookup {@link Trie} and associated data structures. * in the lookup {@link Index.Mutable} and associated data structures.
*/ */
public void setHeaderCacheSize(int headerCacheSize) public void setHeaderCacheSize(int headerCacheSize)
{ {
@ -491,7 +493,7 @@ public class HttpConfiguration implements Dumpable
*/ */
public void addFormEncodedMethod(String method) public void addFormEncodedMethod(String method)
{ {
_formEncodedMethods.put(method, Boolean.TRUE); _formEncodedMethods.put(method,Boolean.TRUE);
} }
/** /**
@ -504,7 +506,7 @@ public class HttpConfiguration implements Dumpable
*/ */
public boolean isFormEncodedMethod(String method) public boolean isFormEncodedMethod(String method)
{ {
return Boolean.TRUE.equals(_formEncodedMethods.get(method)); return _formEncodedMethods.get(method) != null;
} }
/** /**

View File

@ -20,8 +20,10 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -32,10 +34,9 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HandlerContainer; import org.eclipse.jetty.server.HandlerContainer;
import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayUtil; import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.thread.SerializedExecutor; import org.eclipse.jetty.util.thread.SerializedExecutor;
@ -129,44 +130,9 @@ public class ContextHandlerCollection extends HandlerCollection
entry.setValue(sorted); entry.setValue(sorted);
} }
// Loop until we have a big enough trie to hold all the context paths Mapping mapping = new Mapping(handlers, path2Branches);
int capacity = 512;
Mapping mapping;
loop:
while (true)
{
mapping = new Mapping(handlers, capacity);
for (Map.Entry<String, Branch[]> entry : path2Branches.entrySet())
{
if (!mapping._pathBranches.put(entry.getKey().substring(1), entry))
{
capacity += 512;
continue loop;
}
}
break;
}
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ LOG.debug("{}", mapping._pathBranches);
for (String ctx : mapping._pathBranches.keySet())
{
LOG.debug("{}->{}", ctx, Arrays.asList(mapping._pathBranches.get(ctx).getValue()));
}
}
// add new context branches to concurrent map
for (Branch[] branches : path2Branches.values())
{
for (Branch branch : branches)
{
for (ContextHandler context : branch.getContextHandlers())
{
mapping._contextBranches.put(context, branch.getHandler());
}
}
}
return mapping; return mapping;
} }
@ -209,7 +175,7 @@ public class ContextHandlerCollection extends HandlerCollection
// handle many contexts // handle many contexts
if (target.startsWith("/")) if (target.startsWith("/"))
{ {
Trie<Map.Entry<String, Branch[]>> pathBranches = mapping._pathBranches; Index<Map.Entry<String, Branch[]>> pathBranches = mapping._pathBranches;
if (pathBranches == null) if (pathBranches == null)
return; return;
@ -377,13 +343,38 @@ public class ContextHandlerCollection extends HandlerCollection
private static class Mapping extends Handlers private static class Mapping extends Handlers
{ {
private final Map<ContextHandler, Handler> _contextBranches = new HashMap<>(); private final Map<ContextHandler, Handler> _contextBranches;
private final Trie<Map.Entry<String, Branch[]>> _pathBranches; private final Index<Map.Entry<String, Branch[]>> _pathBranches;
private Mapping(Handler[] handlers, int capacity) private Mapping(Handler[] handlers, Map<String, Branch[]> path2Branches)
{ {
super(handlers); super(handlers);
_pathBranches = new ArrayTernaryTrie<>(false, capacity); _pathBranches = new Index.Builder<Map.Entry<String, Branch[]>>()
.caseSensitive(true)
.withAll(() ->
{
Map<String, Map.Entry<String, Branch[]>> result = new LinkedHashMap<>();
for (Map.Entry<String, Branch[]> entry : path2Branches.entrySet())
{
result.put(entry.getKey().substring(1), entry);
}
return result;
})
.build();
// add new context branches to map
Map<ContextHandler, Handler> contextBranches = new HashMap<>();
for (Branch[] branches : path2Branches.values())
{
for (Branch branch : branches)
{
for (ContextHandler context : branch.getContextHandlers())
{
contextBranches.put(context, branch.getHandler());
}
}
}
_contextBranches = Collections.unmodifiableMap(contextBranches);
} }
} }
} }

View File

@ -26,10 +26,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ajax.JSON.Convertible; import org.eclipse.jetty.util.ajax.JSON.Convertible;
@ -79,7 +78,7 @@ public class AsyncJSON
*/ */
public static class Factory public static class Factory
{ {
private Trie<CachedString> cache; private Index.Mutable<CachedString> cache;
private Map<String, Convertor> convertors; private Map<String, Convertor> convertors;
private boolean detailedParseException; private boolean detailedParseException;
@ -106,7 +105,10 @@ public class AsyncJSON
public boolean cache(String value) public boolean cache(String value)
{ {
if (cache == null) if (cache == null)
cache = new ArrayTernaryTrie.Growing<>(false, 64, 64); cache = new Index.Builder<CachedString>()
.caseSensitive(true)
.mutable()
.build();
CachedString cached = new CachedString(value); CachedString cached = new CachedString(value);
if (cached.isCacheable()) if (cached.isCacheable())

View File

@ -20,6 +20,11 @@ package org.eclipse.jetty.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* Abstract Trie implementation. * Abstract Trie implementation.
@ -29,7 +34,7 @@ import java.nio.charset.StandardCharsets;
* *
* @param <V> the type of object that the Trie holds * @param <V> the type of object that the Trie holds
*/ */
public abstract class AbstractTrie<V> implements Trie<V> abstract class AbstractTrie<V> implements Index.Mutable<V>
{ {
final boolean _caseInsensitive; final boolean _caseInsensitive;
@ -38,13 +43,16 @@ public abstract class AbstractTrie<V> implements Trie<V>
_caseInsensitive = insensitive; _caseInsensitive = insensitive;
} }
@Override public boolean isCaseInsensitive()
{
return _caseInsensitive;
}
public boolean put(V v) public boolean put(V v)
{ {
return put(v.toString(), v); return put(v.toString(), v);
} }
@Override
public V remove(String s) public V remove(String s)
{ {
V o = get(s); V o = get(s);
@ -52,33 +60,109 @@ public abstract class AbstractTrie<V> implements Trie<V>
return o; return o;
} }
@Override
public V get(String s) public V get(String s)
{ {
return get(s, 0, s.length()); return get(s, 0, s.length());
} }
@Override
public V get(ByteBuffer b) public V get(ByteBuffer b)
{ {
return get(b, 0, b.remaining()); return get(b, 0, b.remaining());
} }
@Override
public V getBest(String s) public V getBest(String s)
{ {
return getBest(s, 0, s.length()); return getBest(s, 0, s.length());
} }
@Override
public V getBest(byte[] b, int offset, int len) public V getBest(byte[] b, int offset, int len)
{ {
return getBest(new String(b, offset, len, StandardCharsets.ISO_8859_1)); return getBest(new String(b, offset, len, StandardCharsets.ISO_8859_1));
} }
@Override /**
public boolean isCaseInsensitive() * Calculate required Trie capacity in nodes of a tree decomposition of the keys.
* For example given the keys:
* <ul>
* <li>utf_16</li>
* <li>utf_8</li>
* <li>utf16</li>
* <li>utf8</li>
* </ul>
* The tree has 10 nodes as follows:
* <pre>
* 1 - 6
* /
* _ - 8
* /
* u - t - f - 1 - 6
* \
* 8
* </pre>
* @param keys The keys to be put in a Trie
* @param caseSensitive true if the capacity should be calculated with case-sensitive keys
* @return The capacity in nodes of a tree decomposition
*/
protected static int requiredCapacity(Set<String> keys, boolean caseSensitive)
{ {
return _caseInsensitive; List<String> list = caseSensitive
? new ArrayList<>(keys)
: keys.stream().map(String::toLowerCase).collect(Collectors.toList());
Collections.sort(list);
return AbstractTrie.requiredCapacity(list, 0, list.size(), 0);
}
/**
* Calculate required Trie capacity in nodes of a sub-tree decomposition of the keys.
* @param keys The keys to calculate the capacity for
* @param offset The offset of the first key to be considered
* @param length The number of keys to be considered
* @param index The character to be considered
* @return The capacity in tree nodes of the substree
*/
private static int requiredCapacity(List<String> keys, int offset, int length, int index)
{
int required = 0;
// Examine all the keys in the subtree
Character nodeChar = null;
for (int i = 0; i < length; i++)
{
String k = keys.get(offset + i);
// If the key is shorter than our current index then ignore it
if (k.length() <= index)
continue;
// Get the character at the index of the current key
char c = k.charAt(index);
// If the character is the same as the current node, then we are
// still in the current node and need to continue searching for the
// next node or the end of the keys
if (nodeChar != null && c == nodeChar)
continue;
// The character is a new node, so increase required by 1
required++;
// if we had a previous node, then add the required nodes for the subtree under it.
if (nodeChar != null)
required += AbstractTrie.requiredCapacity(keys, offset, i, index + 1);
// set the char for the new node
nodeChar = c;
// reset the offset, length and index to continue iteration from the start of the new node
offset += i;
length -= i;
i = 0;
}
// If we finish the iteration with a nodeChar, then we must add the required nodes for the subtree under it.
if (nodeChar != null)
required += AbstractTrie.requiredCapacity(keys, offset, length, index + 1);
return required;
} }
} }

View File

@ -23,6 +23,7 @@ import java.util.AbstractMap;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
@ -57,11 +58,11 @@ import java.util.Set;
* *
* @param <V> the Entry type * @param <V> the Entry type
*/ */
public class ArrayTernaryTrie<V> extends AbstractTrie<V> class ArrayTernaryTrie<V> extends AbstractTrie<V>
{ {
private static int LO = 1; private static final int LO = 1;
private static int EQ = 2; private static final int EQ = 2;
private static int HI = 3; private static final int HI = 3;
/** /**
* The Size of a Trie row is the char, and the low, equal and high * The Size of a Trie row is the char, and the low, equal and high
@ -69,6 +70,13 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
*/ */
private static final int ROW_SIZE = 4; private static final int ROW_SIZE = 4;
/**
* The maximum capacity of the implementation. Over that,
* the 16 bit indexes can overflow and the trie
* cannot find existing entries anymore.
*/
private static final int MAX_CAPACITY = 21_000;
/** /**
* The Trie rows in a single array which allows a lookup of row,character * The Trie rows in a single array which allows a lookup of row,character
* to the next row in the Trie. This is actually a 2 dimensional * to the next row in the Trie. This is actually a 2 dimensional
@ -93,39 +101,6 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
*/ */
private char _rows; private char _rows;
/**
* Create a case insensitive Trie of default capacity.
*/
public ArrayTernaryTrie()
{
this(128);
}
/**
* Create a Trie of default capacity
*
* @param insensitive true if the Trie is insensitive to the case of the key.
*/
public ArrayTernaryTrie(boolean insensitive)
{
this(insensitive, 128);
}
/**
* Create a case insensitive Trie
*
* @param capacity The capacity of the Trie, which is in the worst case
* is the total number of characters of all keys stored in the Trie.
* The capacity needed is dependent of the shared prefixes of the keys.
* For example, a capacity of 6 nodes is required to store keys "foo"
* and "bar", but a capacity of only 4 is required to
* store "bar" and "bat".
*/
public ArrayTernaryTrie(int capacity)
{
this(true, capacity);
}
/** /**
* Create a Trie * Create a Trie
* *
@ -137,28 +112,37 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
* and "bar", but a capacity of only 4 is required to * and "bar", but a capacity of only 4 is required to
* store "bar" and "bat". * store "bar" and "bat".
*/ */
public ArrayTernaryTrie(boolean insensitive, int capacity) @SuppressWarnings("unchecked")
ArrayTernaryTrie(boolean insensitive, int capacity)
{ {
super(insensitive); super(insensitive);
if (capacity > MAX_CAPACITY)
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
_value = (V[])new Object[capacity]; _value = (V[])new Object[capacity];
_tree = new char[capacity * ROW_SIZE]; _tree = new char[capacity * ROW_SIZE];
_key = new String[capacity]; _key = new String[capacity];
} }
/** @SuppressWarnings("unchecked")
* Copy Trie and change capacity by a factor ArrayTernaryTrie(boolean insensitive, Map<String, V> initialValues)
*
* @param trie the trie to copy from
* @param factor the factor to grow the capacity by
*/
public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
{ {
super(trie.isCaseInsensitive()); super(insensitive);
int capacity = (int)(trie._value.length * factor); // The calculated requiredCapacity does not take into account the
_rows = trie._rows; // extra reserved slot for the empty string key, nor the slots
_value = Arrays.copyOf(trie._value, capacity); // required for 'terminating' the entry (1 slot per key) so we
_tree = Arrays.copyOf(trie._tree, capacity * ROW_SIZE); // have to add those.
_key = Arrays.copyOf(trie._key, capacity); Set<String> keys = initialValues.keySet();
int capacity = AbstractTrie.requiredCapacity(keys, !insensitive) + keys.size() + 1;
if (capacity > MAX_CAPACITY)
throw new IllegalArgumentException("ArrayTernaryTrie maximum capacity overflow (" + capacity + " > " + MAX_CAPACITY + ")");
_value = (V[])new Object[capacity];
_tree = new char[capacity * ROW_SIZE];
_key = new String[capacity];
for (Map.Entry<String, V> entry : initialValues.entrySet())
{
if (!put(entry.getKey(), entry.getValue()))
throw new AssertionError("Invalid capacity calculated (" + capacity + ") at '" + entry + "' for " + initialValues);
}
} }
@Override @Override
@ -527,12 +511,6 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
return entries; return entries;
} }
@Override
public boolean isFull()
{
return _rows + 1 == _key.length;
}
public static int hilo(int diff) public static int hilo(int diff)
{ {
// branchless equivalent to return ((diff<0)?LO:HI); // branchless equivalent to return ((diff<0)?LO:HI);
@ -556,24 +534,14 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
} }
} }
public static class Growing<V> implements Trie<V> static class Growing<V> extends AbstractTrie<V>
{ {
private final int _growby; private final int _growby;
private ArrayTernaryTrie<V> _trie; private ArrayTernaryTrie<V> _trie;
public Growing() Growing(boolean insensitive, int capacity, int growby)
{
this(1024, 1024);
}
public Growing(int capacity, int growby)
{
_growby = growby;
_trie = new ArrayTernaryTrie<>(capacity);
}
public Growing(boolean insensitive, int capacity, int growby)
{ {
super(insensitive);
_growby = growby; _growby = growby;
_trie = new ArrayTernaryTrie<>(insensitive, capacity); _trie = new ArrayTernaryTrie<>(insensitive, capacity);
} }
@ -591,15 +559,14 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
} }
@Override @Override
public boolean isCaseInsensitive() public boolean equals(Object o)
{ {
return _trie.isCaseInsensitive(); if (this == o)
} return true;
if (o == null || getClass() != o.getClass())
@Override return false;
public boolean equals(Object obj) Growing<?> growing = (Growing<?>)o;
{ return Objects.equals(_trie, growing._trie);
return _trie.equals(obj);
} }
@Override @Override
@ -692,12 +659,6 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
return _trie.keySet(); return _trie.keySet();
} }
@Override
public boolean isFull()
{
return false;
}
public void dump() public void dump()
{ {
_trie.dump(); _trie.dump();

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -47,7 +48,7 @@ import java.util.Set;
* *
* @param <V> the entry type * @param <V> the entry type
*/ */
public class ArrayTrie<V> extends AbstractTrie<V> class ArrayTrie<V> extends AbstractTrie<V>
{ {
/** /**
* The Size of a Trie row is how many characters can be looked * The Size of a Trie row is how many characters can be looked
@ -112,11 +113,6 @@ public class ArrayTrie<V> extends AbstractTrie<V>
*/ */
private char _rows; private char _rows;
public ArrayTrie()
{
this(128);
}
/** /**
* @param capacity The capacity of the trie, which at the worst case * @param capacity The capacity of the trie, which at the worst case
* is the total number of characters of all keys stored in the Trie. * is the total number of characters of all keys stored in the Trie.
@ -126,14 +122,32 @@ public class ArrayTrie<V> extends AbstractTrie<V>
* store "bar" and "bat". * store "bar" and "bat".
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ArrayTrie(int capacity) ArrayTrie(int capacity)
{ {
super(true); super(true);
capacity++;
_value = (V[])new Object[capacity]; _value = (V[])new Object[capacity];
_rowIndex = new char[capacity * 32]; _rowIndex = new char[capacity * ROW_SIZE];
_key = new String[capacity]; _key = new String[capacity];
} }
@SuppressWarnings("unchecked")
ArrayTrie(Map<String, V> initialValues)
{
super(true);
// The calculated requiredCapacity does not take into account the
// extra reserved slot for the empty string key, so we have to add 1.
int capacity = requiredCapacity(initialValues.keySet(), false) + 1;
_value = (V[])new Object[capacity];
_rowIndex = new char[capacity * ROW_SIZE];
_key = new String[capacity];
for (Map.Entry<String, V> entry : initialValues.entrySet())
{
if (!put(entry.getKey(), entry.getValue()))
throw new AssertionError("Invalid capacity calculated (" + capacity + ") at '" + entry + "' for " + initialValues);
}
}
@Override @Override
public void clear() public void clear()
{ {
@ -446,6 +460,18 @@ public class ArrayTrie<V> extends AbstractTrie<V>
return keys; return keys;
} }
@Override
public int size()
{
return keySet().size();
}
@Override
public boolean isEmpty()
{
return keySet().isEmpty();
}
private void keySet(Set<String> set, int t) private void keySet(Set<String> set, int t)
{ {
if (t < _value.length && _value[t] != null) if (t < _value.length && _value[t] != null)
@ -468,10 +494,4 @@ public class ArrayTrie<V> extends AbstractTrie<V>
} }
} }
} }
@Override
public boolean isFull()
{
return _rows + 1 >= _key.length;
}
} }

View File

@ -0,0 +1,99 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Set;
/**
* An empty trie implementation that never contains anything and never accepts new entries.
* @param <V> the entry type
*/
class EmptyTrie<V> extends AbstractTrie<V>
{
@SuppressWarnings("rawtypes")
private static final EmptyTrie SENSITIVE = new EmptyTrie<>(false);
@SuppressWarnings("rawtypes")
private static final EmptyTrie INSENSITIVE = new EmptyTrie<>(true);
@SuppressWarnings("unchecked")
public static <V> EmptyTrie<V> instance(boolean caseSensitive)
{
return caseSensitive ? SENSITIVE : INSENSITIVE;
}
private EmptyTrie(boolean insensitive)
{
super(insensitive);
}
@Override
public boolean put(String s, V v)
{
return false;
}
@Override
public V get(String s, int offset, int len)
{
return null;
}
@Override
public V get(ByteBuffer b, int offset, int len)
{
return null;
}
@Override
public V getBest(String s, int offset, int len)
{
return null;
}
@Override
public V getBest(ByteBuffer b, int offset, int len)
{
return null;
}
@Override
public boolean isEmpty()
{
return true;
}
@Override
public Set<String> keySet()
{
return Collections.emptySet();
}
@Override
public int size()
{
return 0;
}
@Override
public void clear()
{
}
}

View File

@ -0,0 +1,364 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* An immutable String lookup data structure.
* @param <V> the entry type
*/
public interface Index<V>
{
/**
* Get an exact match from a String key
*
* @param s The key
* @return the value for the string key
*/
V get(String s);
/**
* Get an exact match from a segment of a ByteBuufer as key
*
* @param b The buffer
* @return The value or null if not found
*/
V get(ByteBuffer b);
/**
* Get an exact match from a String key
*
* @param s The key
* @param offset The offset within the string of the key
* @param len the length of the key
* @return the value for the string / offset / length
*/
V get(String s, int offset, int len);
/**
* Get an exact match from a segment of a ByteBuufer as key
*
* @param b The buffer
* @param offset The offset within the buffer of the key
* @param len the length of the key
* @return The value or null if not found
*/
V get(ByteBuffer b, int offset, int len);
/**
* Get the best match from key in a String.
*
* @param s The string
* @param offset The offset within the string of the key
* @param len the length of the key
* @return The value or null if not found
*/
V getBest(String s, int offset, int len);
/**
* Get the best match from key in a byte buffer.
* The key is assumed to by ISO_8859_1 characters.
*
* @param b The buffer
* @param offset The offset within the buffer of the key
* @param len the length of the key
* @return The value or null if not found
*/
V getBest(ByteBuffer b, int offset, int len);
/**
* Get the best match from key in a String.
*
* @param s The string
* @return The value or null if not found
*/
V getBest(String s);
/**
* Get the best match from key in a byte array.
* The key is assumed to by ISO_8859_1 characters.
*
* @param b The buffer
* @param offset The offset within the array of the key
* @param len the length of the key
* @return The value or null if not found
*/
V getBest(byte[] b, int offset, int len);
/**
* Check if the index contains any entry.
*
* @return true if the index does not contain any entry.
*/
boolean isEmpty();
/**
* Get the number of entries in the index.
*
* @return the index' entries count.
*/
int size();
/**
* Get a {@link Set} of the keys contained in this index.
*
* @return a {@link Set} of the keys contained in this index.
*/
Set<String> keySet();
/**
* A mutable String lookup data structure.
* Implementations are not thread-safe.
* @param <V> the entry type
*/
interface Mutable<V> extends Index<V>
{
/**
* Put an entry into the index.
*
* @param s The key for the entry
* @param v The value of the entry
* @return True if the index had capacity to add the field.
*/
boolean put(String s, V v);
/**
* Put a value as both a key and a value.
*
* @param v The value and key
* @return True if the Trie had capacity to add the field.
*/
boolean put(V v);
/**
* Remove an entry from the index.
*
* @param s The key for the entry
* @return The removed value of the entry
*/
V remove(String s);
/**
* Remove all entries from the index.
*/
void clear();
/**
* Builder of {@link Index.Mutable} instances. Such builder cannot be
* directly created, it is instead returned by calling {@link Index.Builder#mutable()}.
* @param <V> the entry type
*/
class Builder<V> extends Index.Builder<V>
{
private int maxCapacity = -1;
Builder(boolean caseSensitive, Map<String, V> contents)
{
super(caseSensitive, contents);
}
/**
* Configure a maximum capacity for the mutable index.
* A negative value means there is no capacity limit and
* the index can grow without limits.
* The default value is -1.
* @param capacity the maximum capacity of the index.
* @return this
*/
public Builder<V> maxCapacity(int capacity)
{
this.maxCapacity = capacity;
return this;
}
/**
* Build a {@link Mutable} instance.
* @return a {@link Mutable} instance.
*/
public Mutable<V> build()
{
if (contents != null && maxCapacity == 0)
throw new IllegalStateException("Cannot create a mutable index with maxCapacity=0 and some contents");
// TODO we need to consider large size and alphabet when picking a trie impl
Mutable<V> result;
if (maxCapacity > 0)
{
result = new ArrayTernaryTrie<>(!caseSensitive, maxCapacity);
}
else if (maxCapacity < 0)
{
if (caseSensitive)
result = new ArrayTernaryTrie.Growing<>(false, 512, 512);
else
result = new TreeTrie<>();
}
else
{
result = EmptyTrie.instance(caseSensitive);
}
if (contents != null)
{
for (Map.Entry<String, V> entry : contents.entrySet())
{
if (!result.put(entry.getKey(), entry.getValue()))
throw new AssertionError("Index capacity exceeded at " + entry.getKey());
}
}
return result;
}
}
}
/**
* Builder of {@link Index} instances.
* @param <V> the entry type
*/
class Builder<V>
{
Map<String, V> contents;
boolean caseSensitive;
/**
* Create a new index builder instance.
*/
public Builder()
{
this(false, null);
}
Builder(boolean caseSensitive, Map<String, V> contents)
{
this.caseSensitive = caseSensitive;
this.contents = contents;
}
private Map<String, V> contents()
{
if (contents == null)
contents = new LinkedHashMap<>();
return contents;
}
/**
* Configure the index to be either case-sensitive or not.
* Default value is false.
*
* @param caseSensitive true if the index has to be case-sensitive
* @return this
*/
public Builder<V> caseSensitive(boolean caseSensitive)
{
this.caseSensitive = caseSensitive;
return this;
}
/**
* Configure some pre-existing entries.
*
* @param values an array of values
* @param keyFunction a {@link Function} that generates the key of each
* entry of the values array
* @return this
*/
public Builder<V> withAll(V[] values, Function<V, String> keyFunction)
{
for (V value : values)
{
String key = keyFunction.apply(value);
contents().put(key, value);
}
return this;
}
/**
* Configure some pre-existing entries.
*
* @param entriesSupplier a {@link Map} {@link Supplier} of entries
* @return this
*/
public Builder<V> withAll(Supplier<Map<String, V>> entriesSupplier)
{
Map<String, V> map = entriesSupplier.get();
contents().putAll(map);
return this;
}
/**
* Configure a pre-existing entry with a key
* that is the {@link #toString()} representation
* of the value.
*
* @param value The value
* @return this
*/
public Builder<V> with(V value)
{
contents().put(value.toString(), value);
return this;
}
/**
* Configure a pre-existing entry.
*
* @param key The key
* @param value The value for the key string
* @return this
*/
public Builder<V> with(String key, V value)
{
contents().put(key, value);
return this;
}
/**
* Configure the index to be mutable.
*
* @return a {@link Mutable.Builder} configured like this builder.
*/
public Mutable.Builder<V> mutable()
{
return new Mutable.Builder<>(caseSensitive, contents);
}
/**
* Build a {@link Index} instance.
*
* @return a {@link Index} instance.
*/
public Index<V> build()
{
if (contents == null)
return EmptyTrie.instance(caseSensitive);
// TODO we need to consider large size and alphabet when picking a trie impl
if (caseSensitive)
return new ArrayTernaryTrie<>(false, contents);
else
return new ArrayTrie<>(contents);
}
}
}

View File

@ -492,6 +492,6 @@ public abstract class IteratingCallback implements Callback
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s[%s]", super.toString(), _state); return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), _state);
} }
} }

View File

@ -249,6 +249,11 @@ public class Pool<T> implements AutoCloseable, Dumpable
{ {
LOGGER.trace("IGNORED", e); LOGGER.trace("IGNORED", e);
size = entries.size(); size = entries.size();
// Size can be 0 when the pool is in the middle of
// acquiring a connection while another thread
// removes the last one from the pool.
if (size == 0)
break;
} }
index = (index + 1) % size; index = (index + 1) % size;
} }

View File

@ -33,8 +33,6 @@ import java.util.List;
*/ */
public class StringUtil public class StringUtil
{ {
private static final Trie<String> CHARSETS = new ArrayTrie<>(256);
public static final String ALL_INTERFACES = "0.0.0.0"; public static final String ALL_INTERFACES = "0.0.0.0";
public static final String CRLF = "\r\n"; public static final String CRLF = "\r\n";
public static final String DEFAULT_DELIMS = ",;"; public static final String DEFAULT_DELIMS = ",;";
@ -43,15 +41,15 @@ public class StringUtil
public static final String __UTF8 = "utf-8"; public static final String __UTF8 = "utf-8";
public static final String __UTF16 = "utf-16"; public static final String __UTF16 = "utf-16";
static private static final Index<String> CHARSETS = new Index.Builder<String>()
{ .caseSensitive(false)
CHARSETS.put("utf-8", __UTF8); .with("utf-8", __UTF8)
CHARSETS.put("utf8", __UTF8); .with("utf8", __UTF8)
CHARSETS.put("utf-16", __UTF16); .with("utf-16", __UTF16)
CHARSETS.put("utf16", __UTF16); .with("utf16", __UTF16)
CHARSETS.put("iso-8859-1", __ISO_8859_1); .with("iso-8859-1", __ISO_8859_1)
CHARSETS.put("iso_8859_1", __ISO_8859_1); .with("iso_8859_1", __ISO_8859_1)
} .build();
/** /**
* Convert alternate charset names (eg utf8) to normalized * Convert alternate charset names (eg utf8) to normalized

View File

@ -42,7 +42,7 @@ import java.util.Set;
* *
* @param <V> the entry type * @param <V> the entry type
*/ */
public class TreeTrie<V> extends AbstractTrie<V> class TreeTrie<V> extends AbstractTrie<V>
{ {
private static final int[] LOOKUP = private static final int[] LOOKUP =
{ {
@ -63,13 +63,15 @@ public class TreeTrie<V> extends AbstractTrie<V>
private String _key; private String _key;
private V _value; private V _value;
public TreeTrie() @SuppressWarnings("unchecked")
TreeTrie()
{ {
super(true); super(true);
_nextIndex = new TreeTrie[INDEX]; _nextIndex = new TreeTrie[INDEX];
_c = 0; _c = 0;
} }
@SuppressWarnings("unchecked")
private TreeTrie(char c) private TreeTrie(char c)
{ {
super(true); super(true);
@ -231,6 +233,18 @@ public class TreeTrie<V> extends AbstractTrie<V>
return t._value; return t._value;
} }
@Override
public boolean isEmpty()
{
return keySet().isEmpty();
}
@Override
public int size()
{
return keySet().size();
}
@Override @Override
public V getBest(String s, int offset, int len) public V getBest(String s, int offset, int len)
{ {
@ -394,10 +408,4 @@ public class TreeTrie<V> extends AbstractTrie<V>
} }
} }
} }
@Override
public boolean isFull()
{
return false;
}
} }

View File

@ -1,230 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Set;
/**
* A Trie String lookup data structure.
*
* @param <V> the Trie entry type
*/
public interface Trie<V>
{
/**
* Put an entry into the Trie
*
* @param s The key for the entry
* @param v The value of the entry
* @return True if the Trie had capacity to add the field.
*/
public boolean put(String s, V v);
/**
* Put a value as both a key and a value.
*
* @param v The value and key
* @return True if the Trie had capacity to add the field.
*/
public boolean put(V v);
public V remove(String s);
/**
* Get an exact match from a String key
*
* @param s The key
* @return the value for the string key
*/
public V get(String s);
/**
* Get an exact match from a String key
*
* @param s The key
* @param offset The offset within the string of the key
* @param len the length of the key
* @return the value for the string / offset / length
*/
public V get(String s, int offset, int len);
/**
* Get an exact match from a segment of a ByteBuufer as key
*
* @param b The buffer
* @return The value or null if not found
*/
public V get(ByteBuffer b);
/**
* Get an exact match from a segment of a ByteBuufer as key
*
* @param b The buffer
* @param offset The offset within the buffer of the key
* @param len the length of the key
* @return The value or null if not found
*/
public V get(ByteBuffer b, int offset, int len);
/**
* Get the best match from key in a String.
*
* @param s The string
* @return The value or null if not found
*/
public V getBest(String s);
/**
* Get the best match from key in a String.
*
* @param s The string
* @param offset The offset within the string of the key
* @param len the length of the key
* @return The value or null if not found
*/
public V getBest(String s, int offset, int len);
/**
* Get the best match from key in a byte array.
* The key is assumed to by ISO_8859_1 characters.
*
* @param b The buffer
* @param offset The offset within the array of the key
* @param len the length of the key
* @return The value or null if not found
*/
public V getBest(byte[] b, int offset, int len);
/**
* Get the best match from key in a byte buffer.
* The key is assumed to by ISO_8859_1 characters.
*
* @param b The buffer
* @param offset The offset within the buffer of the key
* @param len the length of the key
* @return The value or null if not found
*/
public V getBest(ByteBuffer b, int offset, int len);
public Set<String> keySet();
public boolean isFull();
public boolean isCaseInsensitive();
public void clear();
static <T> Trie<T> empty(final boolean caseInsensitive)
{
return new Trie<T>()
{
@Override
public boolean put(String s, Object o)
{
return false;
}
@Override
public boolean put(Object o)
{
return false;
}
@Override
public T remove(String s)
{
return null;
}
@Override
public T get(String s)
{
return null;
}
@Override
public T get(String s, int offset, int len)
{
return null;
}
@Override
public T get(ByteBuffer b)
{
return null;
}
@Override
public T get(ByteBuffer b, int offset, int len)
{
return null;
}
@Override
public T getBest(String s)
{
return null;
}
@Override
public T getBest(String s, int offset, int len)
{
return null;
}
@Override
public T getBest(byte[] b, int offset, int len)
{
return null;
}
@Override
public T getBest(ByteBuffer b, int offset, int len)
{
return null;
}
@Override
public Set<String> keySet()
{
return Collections.emptySet();
}
@Override
public boolean isFull()
{
return true;
}
@Override
public boolean isCaseInsensitive()
{
return caseInsensitive;
}
@Override
public void clear()
{
}
};
}
}

View File

@ -0,0 +1,65 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
public class IndexTest
{
@Test
public void belowMaxCapacityTest()
{
int size = 10_450;
Index.Builder<Integer> builder = new Index.Builder<>();
builder.caseSensitive(true);
for (int i = 0; i < size; i++)
{
builder.with("/test/group" + i, i);
}
Index<Integer> index = builder.build();
for (int i = 0; i < size; i++)
{
Integer integer = index.get("/test/group" + i);
if (integer == null)
fail("missing entry for '/test/group" + i + "'");
else if (integer != i)
fail("incorrect value for '/test/group" + i + "' (" + integer + ")");
}
}
@Test
public void overMaxCapacityTest()
{
int size = 11_000;
Index.Builder<Integer> builder = new Index.Builder<>();
builder.caseSensitive(true);
for (int i = 0; i < size; i++)
{
builder.with("/test/group" + i, i);
}
assertThrows(IllegalArgumentException.class, builder::build);
}
}

View File

@ -21,30 +21,32 @@ package org.eclipse.jetty.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import static org.eclipse.jetty.util.AbstractTrie.requiredCapacity;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TrieTest public class TrieTest
{ {
public static Stream<Arguments> implementations() public static Stream<Arguments> implementations()
{ {
List<Trie> impls = new ArrayList<>(); List<AbstractTrie<Integer>> impls = new ArrayList<>();
impls.add(new ArrayTrie<Integer>(128)); impls.add(new ArrayTrie<Integer>(128));
impls.add(new TreeTrie<Integer>()); impls.add(new ArrayTernaryTrie<Integer>(true, 128));
impls.add(new ArrayTernaryTrie<Integer>(128)); impls.add(new ArrayTernaryTrie.Growing<Integer>(true, 128, 128));
for (Trie<Integer> trie : impls) for (AbstractTrie<Integer> trie : impls)
{ {
trie.put("hello", 1); trie.put("hello", 1);
trie.put("He", 2); trie.put("He", 2);
@ -62,26 +64,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testOverflow(Trie<Integer> trie) throws Exception public void testKeySet(AbstractTrie<Integer> trie) throws Exception
{
int i = 0;
while (true)
{
if (++i > 10000)
break; // must not be fixed size
if (!trie.put("prefix" + i, i))
{
assertTrue(trie.isFull());
break;
}
}
assertTrue(!trie.isFull() || !trie.put("overflow", 0));
}
@ParameterizedTest
@MethodSource("implementations")
public void testKeySet(Trie<Integer> trie) throws Exception
{ {
String[] values = new String[]{ String[] values = new String[]{
"hello", "hello",
@ -103,7 +86,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetString(Trie<Integer> trie) throws Exception public void testGetString(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.get("hello").intValue()); assertEquals(1, trie.get("hello").intValue());
assertEquals(2, trie.get("He").intValue()); assertEquals(2, trie.get("He").intValue());
@ -130,7 +113,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBuffer(Trie<Integer> trie) throws Exception public void testGetBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.get(BufferUtil.toBuffer("xhellox"), 1, 5).intValue()); assertEquals(1, trie.get(BufferUtil.toBuffer("xhellox"), 1, 5).intValue());
assertEquals(2, trie.get(BufferUtil.toBuffer("xhellox"), 1, 2).intValue()); assertEquals(2, trie.get(BufferUtil.toBuffer("xhellox"), 1, 2).intValue());
@ -155,7 +138,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetDirectBuffer(Trie<Integer> trie) throws Exception public void testGetDirectBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 5).intValue()); assertEquals(1, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 5).intValue());
assertEquals(2, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 2).intValue()); assertEquals(2, trie.get(BufferUtil.toDirectBuffer("xhellox"), 1, 2).intValue());
@ -180,7 +163,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBestArray(Trie<Integer> trie) throws Exception public void testGetBestArray(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.getBest(StringUtil.getUtf8Bytes("xhelloxxxx"), 1, 8).intValue()); assertEquals(1, trie.getBest(StringUtil.getUtf8Bytes("xhelloxxxx"), 1, 8).intValue());
assertEquals(2, trie.getBest(StringUtil.getUtf8Bytes("xhelxoxxxx"), 1, 8).intValue()); assertEquals(2, trie.getBest(StringUtil.getUtf8Bytes("xhelxoxxxx"), 1, 8).intValue());
@ -198,7 +181,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBestBuffer(Trie<Integer> trie) throws Exception public void testGetBestBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.getBest(BufferUtil.toBuffer("xhelloxxxx"), 1, 8).intValue()); assertEquals(1, trie.getBest(BufferUtil.toBuffer("xhelloxxxx"), 1, 8).intValue());
assertEquals(2, trie.getBest(BufferUtil.toBuffer("xhelxoxxxx"), 1, 8).intValue()); assertEquals(2, trie.getBest(BufferUtil.toBuffer("xhelxoxxxx"), 1, 8).intValue());
@ -219,7 +202,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testGetBestDirectBuffer(Trie<Integer> trie) throws Exception public void testGetBestDirectBuffer(AbstractTrie<Integer> trie) throws Exception
{ {
assertEquals(1, trie.getBest(BufferUtil.toDirectBuffer("xhelloxxxx"), 1, 8).intValue()); assertEquals(1, trie.getBest(BufferUtil.toDirectBuffer("xhelloxxxx"), 1, 8).intValue());
assertEquals(2, trie.getBest(BufferUtil.toDirectBuffer("xhelxoxxxx"), 1, 8).intValue()); assertEquals(2, trie.getBest(BufferUtil.toDirectBuffer("xhelxoxxxx"), 1, 8).intValue());
@ -240,7 +223,7 @@ public class TrieTest
@ParameterizedTest @ParameterizedTest
@MethodSource("implementations") @MethodSource("implementations")
public void testFull(Trie<Integer> trie) throws Exception public void testFull(AbstractTrie<Integer> trie) throws Exception
{ {
if (!(trie instanceof ArrayTrie<?> || trie instanceof ArrayTernaryTrie<?>)) if (!(trie instanceof ArrayTrie<?> || trie instanceof ArrayTernaryTrie<?>))
return; return;
@ -250,4 +233,33 @@ public class TrieTest
testGetBestArray(trie); testGetBestArray(trie);
testGetBestBuffer(trie); testGetBestBuffer(trie);
} }
@Test
public void testRequiredCapacity()
{
assertThat(requiredCapacity(Set.of("ABC", "abc"), true), is(6));
assertThat(requiredCapacity(Set.of("ABC", "abc"), false), is(3));
assertThat(requiredCapacity(Set.of(""), false), is(0));
assertThat(requiredCapacity(Set.of("ABC", ""), false), is(3));
assertThat(requiredCapacity(Set.of("ABC"), false), is(3));
assertThat(requiredCapacity(Set.of("ABC", "XYZ"), false), is(6));
assertThat(requiredCapacity(Set.of("A00", "A11"), false), is(5));
assertThat(requiredCapacity(Set.of("A00", "A01", "A10", "A11"), false), is(7));
assertThat(requiredCapacity(Set.of("A", "AB"), false), is(2));
assertThat(requiredCapacity(Set.of("A", "ABC"), false), is(3));
assertThat(requiredCapacity(Set.of("A", "ABCD"), false), is(4));
assertThat(requiredCapacity(Set.of("AB", "ABC"), false), is(3));
assertThat(requiredCapacity(Set.of("ABC", "ABCD"), false), is(4));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(6));
assertThat(requiredCapacity(Set.of("AB", "A"), false), is(2));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF"), false), is(6));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC"), false), is(6));
assertThat(requiredCapacity(Set.of("ABC", "ABCDEF", "ABX"), false), is(7));
assertThat(requiredCapacity(Set.of("ABCDEF", "ABC", "ABX"), false), is(7));
assertThat(requiredCapacity(Set.of("ADEF", "AQPR4", "AQZ"), false), is(9));
assertThat(requiredCapacity(Set.of("111", "ADEF", "AQPR4", "AQZ", "999"), false), is(15));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8"), false), is(7));
assertThat(requiredCapacity(Set.of("utf-16", "utf-8", "utf16", "utf8"), false), is(10));
assertThat(requiredCapacity(Set.of("utf-8", "utf8", "utf-16", "utf16", "iso-8859-1", "iso_8859_1"), false), is(27));
}
} }

View File

@ -38,8 +38,8 @@ import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.IncludeExcludeSet; import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
@ -180,7 +180,10 @@ public class ClassMatcher extends AbstractSet<String>
public static class ByPackage extends AbstractSet<Entry> implements Predicate<String> public static class ByPackage extends AbstractSet<Entry> implements Predicate<String>
{ {
private final ArrayTernaryTrie.Growing<Entry> _entries = new ArrayTernaryTrie.Growing<>(false, 512, 512); private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.caseSensitive(true)
.mutable()
.build();
@Override @Override
public boolean test(String name) public boolean test(String name)
@ -383,7 +386,10 @@ public class ClassMatcher extends AbstractSet<String>
@SuppressWarnings("serial") @SuppressWarnings("serial")
public static class ByModule extends HashSet<Entry> implements Predicate<URI> public static class ByModule extends HashSet<Entry> implements Predicate<URI>
{ {
private final ArrayTernaryTrie.Growing<Entry> _entries = new ArrayTernaryTrie.Growing<>(false, 512, 512); private final Index.Mutable<Entry> _entries = new Index.Builder<Entry>()
.caseSensitive(true)
.mutable()
.build();
@Override @Override
public boolean test(URI uri) public boolean test(URI uri)

View File

@ -29,23 +29,20 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.util.ArrayTrie; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
/** /**
* Represents an Extension Configuration, as seen during the connection Handshake process. * Represents an Extension Configuration, as seen during the connection Handshake process.
*/ */
public class ExtensionConfig public class ExtensionConfig
{ {
private static final Trie<ExtensionConfig> CACHE = new ArrayTrie<>(512); private static final Index<ExtensionConfig> CACHE = new Index.Builder<ExtensionConfig>()
.caseSensitive(false)
static .with("identity", new ExtensionConfig("identity"))
{ .with("permessage-deflate", new ExtensionConfig("permessage-deflate"))
CACHE.put("identity", new ExtensionConfig("identity")); .with("permessage-deflate; client_max_window_bits", new ExtensionConfig("permessage-deflate; client_max_window_bits"))
CACHE.put("permessage-deflate", new ExtensionConfig("permessage-deflate")); .build();
CACHE.put("permessage-deflate; client_max_window_bits", new ExtensionConfig("permessage-deflate; client_max_window_bits"));
}
/** /**
* Parse a single parameterized name. * Parse a single parameterized name.