diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/HeadersAdvice.java b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/HeadersAdvice.java index f3ed96ca617..e7728a1c5ad 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/HeadersAdvice.java +++ b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/HeadersAdvice.java @@ -4,17 +4,21 @@ import static datadog.trace.instrumentation.httpclient.HttpHeadersInjectAdapter.KEEP; import static datadog.trace.instrumentation.httpclient.HttpHeadersInjectAdapter.SETTER; import static datadog.trace.instrumentation.httpclient.JavaNetClientDecorator.DECORATE; +import static java.lang.String.CASE_INSENSITIVE_ORDER; import java.net.http.HttpHeaders; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; import net.bytebuddy.asm.Advice; public class HeadersAdvice { @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void methodExit(@Advice.Return(readOnly = false) HttpHeaders headers) { - final Map> headerMap = new HashMap<>(headers.map()); + // Note: adding duplicate keys will throw an IllegalArgumentException so we need to dedupe + // case insensitively + final Map> headerMap = new TreeMap<>(CASE_INSENSITIVE_ORDER); + headerMap.putAll(headers.map()); DECORATE.injectContext(getCurrentContext(), headerMap, SETTER); headers = HttpHeaders.of(headerMap, KEEP); } diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JavaHttpClientTest.groovy b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JavaHttpClientTest.groovy index 66813e4757e..d53b1ffba6c 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JavaHttpClientTest.groovy +++ b/dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JavaHttpClientTest.groovy @@ -1,8 +1,9 @@ package datadog.trace.instrumentation.httpclient -import datadog.trace.agent.test.base.HttpClientTest + import static datadog.trace.instrumentation.httpclient.JavaNetClientDecorator.DECORATE +import datadog.trace.agent.test.base.HttpClientTest import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse @@ -42,6 +43,22 @@ abstract class JavaHttpClientTest extends HttpClientTest { boolean testRedirects() { false } + + def 'should not inject duplicate headers'() { + when: + def status = doRequest("GET", server.address.resolve("/success"), + // our codec inject names all lowercase + ["X-Datadog-Trace-ID": "0"]) + + then: + status == 200 + assertTraces(2) { + trace(size(1)) { + clientSpan(it, null) + } + server.distributedRequestTrace(it, trace(0).last()) + } + } } class JavaHttpClientV0Test extends JavaHttpClientTest {