112 lines
4.6 KiB
Java
112 lines
4.6 KiB
Java
package com.baiyue.common.web;
|
|
|
|
import java.security.SecureRandom;
|
|
import java.util.HexFormat;
|
|
import java.util.Optional;
|
|
import java.util.Locale;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Strict W3C {@code traceparent} version 00 value.
|
|
*
|
|
* <p>This type deliberately accepts only the currently frozen version-00 shape. Unsupported,
|
|
* ambiguous or malformed input must start a new trace instead of being partially trusted.</p>
|
|
*/
|
|
public record TraceParent(String traceId, String parentId, String traceFlags) {
|
|
public static final String HEADER = "traceparent";
|
|
public static final int HEADER_LENGTH = 55;
|
|
|
|
private static final Pattern VERSION_00 = Pattern.compile(
|
|
"00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})");
|
|
private static final Pattern LEGACY_TRACE_ID = Pattern.compile("[0-9A-Fa-f]{32}");
|
|
private static final String ZERO_TRACE_ID = "0".repeat(32);
|
|
private static final String ZERO_PARENT_ID = "0".repeat(16);
|
|
private static final SecureRandom RANDOM = new SecureRandom();
|
|
private static final HexFormat HEX = HexFormat.of();
|
|
|
|
public TraceParent {
|
|
if (!validTraceId(traceId) || !validParentId(parentId)
|
|
|| traceFlags == null || !traceFlags.matches("[0-9a-f]{2}")) {
|
|
throw new IllegalArgumentException("Invalid W3C traceparent components");
|
|
}
|
|
}
|
|
|
|
/** Parses one exact, lowercase W3C version-00 header value. */
|
|
public static Optional<TraceParent> parse(String value) {
|
|
if (value == null || value.length() != HEADER_LENGTH) {
|
|
return Optional.empty();
|
|
}
|
|
Matcher matcher = VERSION_00.matcher(value);
|
|
if (!matcher.matches() || !validTraceId(matcher.group(1)) || !validParentId(matcher.group(2))) {
|
|
return Optional.empty();
|
|
}
|
|
return Optional.of(new TraceParent(matcher.group(1), matcher.group(2), matcher.group(3)));
|
|
}
|
|
|
|
/** Creates the local inbound context for a valid remote parent. */
|
|
public static TraceParent childOf(TraceParent remoteParent) {
|
|
return new TraceParent(remoteParent.traceId(), randomNonZero(8), remoteParent.traceFlags());
|
|
}
|
|
|
|
/** Creates a new root context, optionally reusing a valid legacy trace ID. */
|
|
public static TraceParent newRoot(String legacyTraceId) {
|
|
String traceId = validLegacyTraceId(legacyTraceId)
|
|
? legacyTraceId.toLowerCase(java.util.Locale.ROOT) : randomNonZero(16);
|
|
return new TraceParent(traceId, randomNonZero(8), "00");
|
|
}
|
|
|
|
/** Creates a sampled outbound child for a trusted current trace ID. */
|
|
public static TraceParent outboundChild(String currentTraceId) {
|
|
if (!validLegacyTraceId(currentTraceId)) {
|
|
throw new IllegalArgumentException("Current trace ID is invalid");
|
|
}
|
|
return new TraceParent(currentTraceId.toLowerCase(java.util.Locale.ROOT), randomNonZero(8), "01");
|
|
}
|
|
|
|
/**
|
|
* Normalizes a trace ID from a trusted persisted/internal context.
|
|
*
|
|
* <p>Unlike an untrusted HTTP header, a canonical UUID is accepted and compacted because
|
|
* existing event envelopes historically persisted UUID trace IDs.</p>
|
|
*/
|
|
public static Optional<String> normalizeTrustedTraceId(String value) {
|
|
if (validLegacyTraceId(value)) {
|
|
return Optional.of(value.toLowerCase(Locale.ROOT));
|
|
}
|
|
if (value != null && value.matches(
|
|
"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}")) {
|
|
String compact = value.replace("-", "").toLowerCase(Locale.ROOT);
|
|
return validLegacyTraceId(compact) ? Optional.of(compact) : Optional.empty();
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
public static boolean validLegacyTraceId(String value) {
|
|
return value != null && LEGACY_TRACE_ID.matcher(value).matches()
|
|
&& !ZERO_TRACE_ID.equalsIgnoreCase(value);
|
|
}
|
|
|
|
public String value() {
|
|
return "00-" + traceId + '-' + parentId + '-' + traceFlags;
|
|
}
|
|
|
|
private static boolean validTraceId(String value) {
|
|
return value != null && value.matches("[0-9a-f]{32}") && !ZERO_TRACE_ID.equals(value);
|
|
}
|
|
|
|
private static boolean validParentId(String value) {
|
|
return value != null && value.matches("[0-9a-f]{16}") && !ZERO_PARENT_ID.equals(value);
|
|
}
|
|
|
|
private static String randomNonZero(int byteCount) {
|
|
byte[] bytes = new byte[byteCount];
|
|
String value;
|
|
do {
|
|
RANDOM.nextBytes(bytes);
|
|
value = HEX.formatHex(bytes);
|
|
} while (value.chars().allMatch(character -> character == '0'));
|
|
return value;
|
|
}
|
|
}
|