Class Utilities
- Author:
- Mark Allen
-
Method Summary
Modifier and TypeMethodDescriptionstatic ExecutorService
createVirtualThreadsNewThreadPerTaskExecutor
(String threadNamePrefix, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) Provides a virtual-thread-per-task executor service if supported by the runtime.static byte[]
Returns a shared zero-lengthbyte[]
instance.extractCharsetFromHeaders
(Map<String, Set<String>> headers) Extracts theCharset
from the firstContent-Type
header, if present and valid.extractCharsetFromHeaderValue
(String contentTypeHeaderValue) Extracts thecharset=...
parameter from aContent-Type
header value.extractClientUrlPrefixFromHeaders
(Map<String, Set<String>> headers) Best-effort attempt to determine a client's URL prefix by examining request headers.extractContentTypeFromHeaders
(Map<String, Set<String>> headers) Extracts the media type (without parameters) from the firstContent-Type
header.extractContentTypeFromHeaderValue
(String contentTypeHeaderValue) Extracts the media type (without parameters) from aContent-Type
header value.extractCookiesFromHeaders
(Map<String, Set<String>> headers) ParsesCookie
request headers into a map of cookie names to values.Parses anapplication/x-www-form-urlencoded
query string into a multimap of names to values.Extracts query parameters from a URL (or URI string) into a multimap of names to values.localesFromAcceptLanguageHeaderValue
(String acceptLanguageHeaderValue) Parses anAccept-Language
header value into a best-effort ordered list ofLocale
s.static String
Normalizes a URL or path into a canonical request path.static String
trimAggressively
(String string) A "stronger" version ofString.trim()
which discards any kind of whitespace or invisible separator.static String
trimAggressivelyToEmpty
(String string) Aggressively trims Unicode whitespace from the given string and returns""
if the input isnull
.static String
trimAggressivelyToNull
(String string) Aggressively trims Unicode whitespace from the given string and returnsnull
if the result is empty.static Boolean
Does the platform runtime support virtual threads (either Java 19 and 20 w/preview enabled or Java 21+)?
-
Method Details
-
virtualThreadsAvailable
Does the platform runtime support virtual threads (either Java 19 and 20 w/preview enabled or Java 21+)?- Returns:
true
if the runtime supports virtual threads,false
otherwise
-
createVirtualThreadsNewThreadPerTaskExecutor
@Nonnull public static ExecutorService createVirtualThreadsNewThreadPerTaskExecutor(@Nonnull String threadNamePrefix, @Nonnull Thread.UncaughtExceptionHandler uncaughtExceptionHandler) Provides a virtual-thread-per-task executor service if supported by the runtime.In order to support Soklet users who are not yet ready to enable virtual threads (those not running either Java 19 and 20 w/preview enabled or Java 21+), we compile Soklet with a source level < 19 and avoid any hard references to virtual threads by dynamically creating our executor service via
MethodHandle
references.You should not call this method if
virtualThreadsAvailable()
isfalse
.// This method is effectively equivalent to this code return Executors.newThreadPerTaskExecutor( Thread.ofVirtual() .name(threadNamePrefix) .uncaughtExceptionHandler(uncaughtExceptionHandler) .factory() );
- Parameters:
threadNamePrefix
- thread name prefix for the virtual thread factory builderuncaughtExceptionHandler
- uncaught exception handler for the virtual thread factory builder- Returns:
- a virtual-thread-per-task executor service
- Throws:
IllegalStateException
- if the runtime environment does not support virtual threads
-
emptyByteArray
Returns a shared zero-lengthbyte[]
instance.Useful as a sentinel when you need a non-
null
byte array but have no content.- Returns:
- a zero-length byte array (never
null
)
-
extractQueryParametersFromQuery
@Nonnull public static Map<String, Set<String>> extractQueryParametersFromQuery(@Nonnull String query) Parses anapplication/x-www-form-urlencoded
query string into a multimap of names to values.Decodes percent-escapes and
'+'
as space using UTF-8. Pairs missing a name or value are ignored. Multiple occurrences of the same name are collected into aSet
in insertion order (duplicates are de-duplicated).- Parameters:
query
- a raw query string such as"a=1&b=2&b=3"
(must be non-null
)- Returns:
- a map of parameter names to their distinct values, preserving first-seen name order; empty if none
- See Also:
-
extractQueryParametersFromUrl
Extracts query parameters from a URL (or URI string) into a multimap of names to values.If the input is not a valid
URI
, an empty map is returned. The raw query is split on'&'
into name/value pairs, values are split on the first'='
, and both name and value are UTF-8 decoded (percent-escapes and'+'
→ space). Blank pairs and pairs missing either name or value are ignored. Multiple occurrences of the same name are collected into aSet
in insertion order (duplicates are de-duplicated).- Parameters:
url
- an absolute or relative URL/URI string (must be non-null
)- Returns:
- a map of parameter names to their distinct values, preserving first-seen name order; empty if none/invalid
-
extractCookiesFromHeaders
@Nonnull public static Map<String, Set<String>> extractCookiesFromHeaders(@Nonnull Map<String, Set<String>> headers) ParsesCookie
request headers into a map of cookie names to values.Header name matching is case-insensitive (
"Cookie"
vs"cookie"
), but cookie names are case-sensitive. Values are parsed per the following liberal rules:- Components are split on
';'
unless inside a quoted string. - Quoted values have surrounding quotes removed and common backslash escapes unescaped.
- Percent-escapes are decoded as UTF-8.
'+'
is not treated specially.
Set
in insertion order.- Parameters:
headers
- request headers as a multimap of header name to values (must be non-null
)- Returns:
- a map of cookie name to distinct values; empty if no valid cookies are present
- Components are split on
-
normalizedPathForUrl
Normalizes a URL or path into a canonical request path.Behavior:
- If input starts with
http://
orhttps://
, the path portion is extracted. - Ensures the result begins with
'/'
. - Removes any trailing
'/'
(except for the root path'/'
). - Strips any query string.
- Applies aggressive trimming of Unicode whitespace.
- Parameters:
url
- a URL or path to normalize (must be non-null
)- Returns:
- the normalized path (never
null
);"/"
for empty input
- If input starts with
-
localesFromAcceptLanguageHeaderValue
@Nonnull public static List<Locale> localesFromAcceptLanguageHeaderValue(@Nonnull String acceptLanguageHeaderValue) Parses anAccept-Language
header value into a best-effort ordered list ofLocale
s.Quality weights are honored by
Locale.LanguageRange.parse(String)
; results are then mapped to available JVM locales. Unknown or unavailable language ranges are skipped. On parse failure, an empty list is returned.- Parameters:
acceptLanguageHeaderValue
- the raw header value (must be non-null
)- Returns:
- locales in descending preference order; empty if none could be resolved
-
extractClientUrlPrefixFromHeaders
@Nonnull public static Optional<String> extractClientUrlPrefixFromHeaders(@Nonnull Map<String, Set<String>> headers) Best-effort attempt to determine a client's URL prefix by examining request headers.A URL prefix in this context is defined as
<scheme>://host<:optional port>
, but no path or query components.Soklet is generally the "last hop" behind a load balancer/reverse proxy and does get accessed directly by clients.
Normally a load balancer/reverse proxy/other upstream proxies will provide information about the true source of the request through headers like the following:
Host
Forwarded
Origin
X-Forwarded-Proto
X-Forwarded-Protocol
X-Url-Scheme
Front-End-Https
X-Forwarded-Ssl
X-Forwarded-Host
X-Forwarded-Port
This method may take these and other headers into account when determining URL prefix.
For example, the following would be legal URL prefixes returned from this method:
https://www.soklet.com
http://www.fake.com:1234
The following would NOT be legal URL prefixes:
www.soklet.com
(missing protocol)https://www.soklet.com/
(trailing slash)https://www.soklet.com/test
(trailing slash, path)https://www.soklet.com/test?abc=1234
(trailing slash, path, query)
- Parameters:
headers
- HTTP request headers- Returns:
- the URL prefix, or
Optional.empty()
if it could not be determined
-
extractContentTypeFromHeaders
@Nonnull public static Optional<String> extractContentTypeFromHeaders(@Nonnull Map<String, Set<String>> headers) Extracts the media type (without parameters) from the firstContent-Type
header.For example,
"text/html; charset=utf-8"
→"text/html"
.- Parameters:
headers
- request/response headers (must be non-null
)- Returns:
- the media type if present; otherwise
Optional.empty()
- See Also:
-
extractContentTypeFromHeaderValue
@Nonnull public static Optional<String> extractContentTypeFromHeaderValue(@Nullable String contentTypeHeaderValue) Extracts the media type (without parameters) from aContent-Type
header value.For example,
"application/json; charset=utf-8"
→"application/json"
.- Parameters:
contentTypeHeaderValue
- the raw header value; may benull
or blank- Returns:
- the media type if present; otherwise
Optional.empty()
-
extractCharsetFromHeaders
@Nonnull public static Optional<Charset> extractCharsetFromHeaders(@Nonnull Map<String, Set<String>> headers) Extracts theCharset
from the firstContent-Type
header, if present and valid.Tolerates additional parameters and arbitrary whitespace. Invalid or unknown charset tokens yield
Optional.empty()
.- Parameters:
headers
- request/response headers (must be non-null
)- Returns:
- the charset declared by the header; otherwise
Optional.empty()
- See Also:
-
extractCharsetFromHeaderValue
@Nonnull public static Optional<Charset> extractCharsetFromHeaderValue(@Nullable String contentTypeHeaderValue) Extracts thecharset=...
parameter from aContent-Type
header value.Parsing is forgiving: parameters may appear in any order and with arbitrary spacing. If a charset is found, it is validated via
Charset.forName(String)
; invalid names result inOptional.empty()
.- Parameters:
contentTypeHeaderValue
- the raw header value; may benull
or blank- Returns:
- the resolved charset if present and valid; otherwise
Optional.empty()
-
trimAggressively
A "stronger" version ofString.trim()
which discards any kind of whitespace or invisible separator.In a web environment with user-supplied inputs, this is the behavior we want the vast majority of the time. For example, users copy-paste URLs from Microsoft Word or Outlook and it's easy to accidentally include a
U+202F "Narrow No-Break Space (NNBSP)"
character at the end, which might break parsing.See https://www.compart.com/en/unicode/U+202F for details.
- Parameters:
string
- the string to trim- Returns:
- the trimmed string, or
null
if the input string isnull
or the trimmed representation is of length0
-
trimAggressivelyToNull
Aggressively trims Unicode whitespace from the given string and returnsnull
if the result is empty.See
trimAggressively(String)
for details on which code points are removed.- Parameters:
string
- the input string; may benull
- Returns:
- a trimmed, non-empty string; or
null
if input wasnull
or trimmed to empty
-
trimAggressivelyToEmpty
Aggressively trims Unicode whitespace from the given string and returns""
if the input isnull
.See
trimAggressively(String)
for details on which code points are removed.- Parameters:
string
- the input string; may benull
- Returns:
- a trimmed string (never
null
);""
if input wasnull
-