Skip to content

Commit e56b785

Browse files
committed
Avoid duplicate headers when injecting on java.net http client
1 parent 419da21 commit e56b785

File tree

5 files changed

+78
-11
lines changed

5 files changed

+78
-11
lines changed

dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java/datadog/trace/instrumentation/httpclient/HttpHeadersInstrumentation.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public ElementMatcher<TypeDescription> hierarchyMatcher() {
4848
@Override
4949
public String[] helperClassNames() {
5050
return new String[] {
51-
packageName + ".HttpHeadersInjectAdapter", packageName + ".JavaNetClientDecorator",
51+
packageName + ".CaseInsensitiveKey",
52+
packageName + ".HttpHeadersInjectAdapter",
53+
packageName + ".JavaNetClientDecorator",
5254
};
5355
}
5456

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package datadog.trace.instrumentation.httpclient;
2+
3+
import java.util.Locale;
4+
import java.util.Objects;
5+
6+
/**
7+
* A class usable as key in a Set or Map that matches case-insensitively but still contains the
8+
* original value.
9+
*/
10+
public final class CaseInsensitiveKey {
11+
private final String value;
12+
private final String lowercase;
13+
14+
public CaseInsensitiveKey(final String value) {
15+
this.value = value;
16+
this.lowercase = value != null ? value.toLowerCase(Locale.ROOT) : null;
17+
}
18+
19+
public String getValue() {
20+
return value;
21+
}
22+
23+
@Override
24+
public boolean equals(Object o) {
25+
if (!(o instanceof CaseInsensitiveKey)) {
26+
return false;
27+
}
28+
CaseInsensitiveKey that = (CaseInsensitiveKey) o;
29+
return Objects.equals(lowercase, that.lowercase);
30+
}
31+
32+
@Override
33+
public int hashCode() {
34+
return Objects.hashCode(lowercase);
35+
}
36+
}

dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/HeadersAdvice.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,27 @@
66
import static datadog.trace.instrumentation.httpclient.JavaNetClientDecorator.DECORATE;
77

88
import java.net.http.HttpHeaders;
9-
import java.util.HashMap;
9+
import java.util.LinkedHashMap;
1010
import java.util.List;
1111
import java.util.Map;
1212
import net.bytebuddy.asm.Advice;
1313

1414
public class HeadersAdvice {
1515
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
1616
public static void methodExit(@Advice.Return(readOnly = false) HttpHeaders headers) {
17-
final Map<String, List<String>> headerMap = new HashMap<>(headers.map());
17+
final Map<CaseInsensitiveKey, List<String>> headerMap = new LinkedHashMap<>();
18+
// Note: we don't want to modify the case of the current headers
19+
// However adding duplicate keys will throw an IllegalArgumentException so we need to dedupe
20+
// case insensitively
21+
for (final Map.Entry<String, List<String>> entry : headers.map().entrySet()) {
22+
headerMap.put(new CaseInsensitiveKey(entry.getKey()), entry.getValue());
23+
}
1824
DECORATE.injectContext(getCurrentContext(), headerMap, SETTER);
19-
headers = HttpHeaders.of(headerMap, KEEP);
25+
// convert back
26+
final Map<String, List<String>> finalMap = new LinkedHashMap<>();
27+
for (final Map.Entry<CaseInsensitiveKey, List<String>> entry : headerMap.entrySet()) {
28+
finalMap.put(entry.getKey().getValue(), entry.getValue());
29+
}
30+
headers = HttpHeaders.of(finalMap, KEEP);
2031
}
2132
}

dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/main/java11/datadog/trace/instrumentation/httpclient/HttpHeadersInjectAdapter.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@
66
import java.util.Map;
77
import java.util.function.BiPredicate;
88

9-
public class HttpHeadersInjectAdapter implements CarrierSetter<Map<String, List<String>>> {
9+
public class HttpHeadersInjectAdapter
10+
implements CarrierSetter<Map<CaseInsensitiveKey, List<String>>> {
1011

1112
public static final HttpHeadersInjectAdapter SETTER = new HttpHeadersInjectAdapter();
1213
public static final BiPredicate<String, String> KEEP = HttpHeadersInjectAdapter::keep;
1314

14-
@Override
15-
public void set(final Map<String, List<String>> carrier, final String key, final String value) {
16-
carrier.put(key, Collections.singletonList(value));
17-
}
18-
1915
public static boolean keep(String key, String value) {
2016
return true;
2117
}
18+
19+
@Override
20+
public void set(Map<CaseInsensitiveKey, List<String>> carrier, String key, String value) {
21+
carrier.put(new CaseInsensitiveKey(key), Collections.singletonList(value));
22+
}
2223
}

dd-java-agent/instrumentation/java/java-net/java-net-11.0/src/test/groovy/datadog/trace/instrumentation/httpclient/JavaHttpClientTest.groovy

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package datadog.trace.instrumentation.httpclient
22

3-
import datadog.trace.agent.test.base.HttpClientTest
3+
44
import static datadog.trace.instrumentation.httpclient.JavaNetClientDecorator.DECORATE
55

6+
import datadog.trace.agent.test.base.HttpClientTest
67
import java.net.http.HttpClient
78
import java.net.http.HttpRequest
89
import java.net.http.HttpResponse
@@ -42,6 +43,22 @@ abstract class JavaHttpClientTest extends HttpClientTest {
4243
boolean testRedirects() {
4344
false
4445
}
46+
47+
def 'should not inject duplicate headers'() {
48+
when:
49+
def status = doRequest("GET", server.address.resolve("/success"),
50+
// our codec inject names all lowercase
51+
["X-Datadog-Trace-ID": "0"])
52+
53+
then:
54+
status == 200
55+
assertTraces(2) {
56+
trace(size(1)) {
57+
clientSpan(it, null)
58+
}
59+
server.distributedRequestTrace(it, trace(0).last())
60+
}
61+
}
4562
}
4663

4764
class JavaHttpClientV0Test extends JavaHttpClientTest {

0 commit comments

Comments
 (0)