#1874: make message header getter case insensitive for propagated headers (#1884)

This commit is contained in:
Artem Ptushkin
2021-03-17 10:46:55 +01:00
committed by GitHub
parent 984f62c32d
commit 39094e1614
5 changed files with 102 additions and 16 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.instrument.messaging;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -60,26 +61,51 @@ public class MessageHeaderPropagatorGetter implements Propagator.Getter<MessageH
private String doGet(MessageHeaderAccessor accessor, String key) {
if (accessor instanceof NativeMessageHeaderAccessor) {
NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor;
String result = nativeAccessor.getFirstNativeHeader(key);
if (result != null) {
return result;
Map<String, List<String>> nativeHeadersMap = nativeAccessor.toNativeHeaderMap();
if (!nativeHeadersMap.isEmpty()) {
return getFromNativeHeaders(nativeHeadersMap, key);
}
}
else {
Object nativeHeaders = accessor.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS);
if (nativeHeaders instanceof Map) {
Object result = ((Map) nativeHeaders).get(key);
if (result instanceof List && !((List) result).isEmpty()) {
return String.valueOf(((List) result).get(0));
Map nativeHeadersMap = (Map) nativeHeaders;
if (!nativeHeadersMap.isEmpty()) {
return getFromNativeHeaders(nativeHeadersMap, key);
}
}
}
Object result = accessor.getHeader(key);
if (result != null) {
if (result instanceof byte[]) {
return new String((byte[]) result, StandardCharsets.UTF_8);
Set<Map.Entry<String, Object>> headerEntries = accessor.getMessageHeaders().entrySet();
return getFromHeaders(headerEntries, key);
}
private String getFromHeaders(Set<Map.Entry<String, Object>> headerEntries, String key) {
for (Map.Entry<String, Object> entry : headerEntries) {
if (entry.getKey().equalsIgnoreCase(key)) {
Object result = entry.getValue();
if (result != null) {
if (result instanceof byte[]) {
return new String((byte[]) result, StandardCharsets.UTF_8);
}
return result.toString();
}
}
}
return null;
}
private String getFromNativeHeaders(Map nativeHeaders, String key) {
Set<Map.Entry> entrySet = nativeHeaders.entrySet();
for (Map.Entry entries : entrySet) {
if (entries.getKey() instanceof String) {
String headersKey = (String) entries.getKey();
if (headersKey.equalsIgnoreCase(key)) {
Object result = entries.getValue();
if (result instanceof List && !((List) result).isEmpty()) {
return String.valueOf(((List) result).get(0));
}
}
}
return result.toString();
}
return null;
}
@@ -88,5 +114,4 @@ public class MessageHeaderPropagatorGetter implements Propagator.Getter<MessageH
public String toString() {
return "MessageHeaderPropagatorGetter{}";
}
}

View File

@@ -45,6 +45,8 @@ public class MultipleHopsIntegrationTests
static final BaggageField REQUEST_ID = BaggageField.create("x-vcap-request-id");
static final BaggageField COUNTRY_CODE = BaggageField.create("country-code");
static final BaggageField CASE_INSENSITIVE_ID = BaggageField.create("foo-id");
static final BaggageField NOT_PROPAGATED_HEADER = BaggageField.create("baz-id");
@Override
protected void assertSpanNames() {
@@ -61,7 +63,10 @@ public class MultipleHopsIntegrationTests
// baz is not tagged in the initial span, only downstream!
then(this.application.allSpans()).as("All downstream have country-code")
.filteredOn(span -> !span.equals(initialSpan))
.allMatch(span -> "FO".equals(COUNTRY_CODE.getValue(BraveAccessor.traceContext(span.context()))));
// it propagates only and all the `spring.sleuth.baggage.remote-fields` in case insensitive way
.allMatch(span -> "FO".equals(COUNTRY_CODE.getValue(BraveAccessor.traceContext(span.context()))))
.allMatch(span -> "123".equalsIgnoreCase(CASE_INSENSITIVE_ID.getValue(BraveAccessor.traceContext(span.context()))))
.allMatch(span -> NOT_PROPAGATED_HEADER.getValue(BraveAccessor.traceContext(span.context())) == null);
}
@Configuration(proxyBeanMethods = false)

View File

@@ -20,6 +20,9 @@ import java.util.List;
import java.util.Map;
import brave.Tracing;
import brave.baggage.BaggageField;
import brave.baggage.BaggagePropagation;
import brave.baggage.BaggagePropagationConfig;
import brave.propagation.B3Propagation;
import brave.propagation.TraceContext;
import org.junit.jupiter.api.Test;
@@ -46,7 +49,14 @@ public class TracingChannelInterceptorTest
@Override
public Tracing.Builder tracingBuilder() {
return super.tracingBuilder()
.propagationFactory(B3Propagation.newFactoryBuilder().injectFormat(SINGLE).build());
.propagationFactory(BaggagePropagation.newFactoryBuilder(B3Propagation.newFactoryBuilder()
.injectFormat(SINGLE)
.build()
)
.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create("Foo-Id")))
.add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create("Baz-Id")))
.build()
);
}
};
this.testTracing.reset();
@@ -67,7 +77,7 @@ public class TracingChannelInterceptorTest
TraceContext receiveContext = parseB3SingleFormat(
((List) ((Map) this.channel.receive().getHeaders().get(NATIVE_HEADERS)).get("b3")).get(0).toString())
.context();
.context();
assertThat(receiveContext.parentIdString()).isEqualTo("000000000000000b");
}

View File

@@ -52,7 +52,7 @@ import static org.assertj.core.api.BDDAssertions.then;
import static org.awaitility.Awaitility.await;
@ContextConfiguration(classes = MultipleHopsIntegrationTests.TestConfig.class)
@TestPropertySource(properties = { "spring.sleuth.baggage.remote-fields=x-vcap-request-id,country-code",
@TestPropertySource(properties = { "spring.sleuth.baggage.remote-fields=x-vcap-request-id,country-code,Foo-Id",
"spring.sleuth.baggage.local-fields=bp", "spring.sleuth.integration.enabled=true" })
public abstract class MultipleHopsIntegrationTests {
@@ -62,6 +62,10 @@ public abstract class MultipleHopsIntegrationTests {
protected static final String COUNTRY_CODE = "country-code";
protected static final String CASE_INSENSITIVE_ID = "Foo-Id";
protected static final String NOT_PROPAGATED_HEADER = "baz-id";
@Autowired
Tracer tracer;
@@ -117,6 +121,8 @@ public abstract class MultipleHopsIntegrationTests {
// set request ID in a header not with the api explicitly
HttpHeaders headers = new HttpHeaders();
headers.put(REQUEST_ID, Collections.singletonList("f4308d05-2228-4468-80f6-92a8377ba193"));
headers.put(CASE_INSENSITIVE_ID, Collections.singletonList("123"));
headers.put(NOT_PROPAGATED_HEADER, Collections.singletonList("456"));
RequestEntity requestEntity = new RequestEntity(headers, HttpMethod.GET,
URI.create("http://localhost:" + this.testConfig.port + "/greeting"));
this.restTemplate.exchange(requestEntity, String.class);

View File

@@ -45,8 +45,10 @@ import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.StringUtils;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.messaging.support.NativeMessageHeaderAccessor.NATIVE_HEADERS;
@@ -348,6 +350,44 @@ public abstract class TracingChannelInterceptorTest implements TestTracingAwareS
assertThat(this.spans).extracting(FinishedSpan::getRemoteServiceName).containsOnly("broker", null);
}
@Test
public void should_propagate_headers_case_insensitive() {
channel.addInterceptor(this.interceptor);
Map<String, Object> headers = new HashMap<>();
headers.put("Foo-Id", "123");
headers.put("baz-id", "456");
channel.send(MessageBuilder.createMessage("foo", new MessageHeaders(headers)));
Message<?> actualMessage = channel.receive();
assertThat(actualMessage.getHeaders()).isNotEmpty();
assertThat(actualMessage.getHeaders().get("not-propagated-header")).isNull();
assertThat(actualMessage.getHeaders().get("Foo-Id")).isEqualTo("123");
assertThat(actualMessage.getHeaders().get("baz-id")).isEqualTo("456");
}
@Test
public void should_propagate_native_headers_case_insensitive() {
channel.addInterceptor(this.interceptor);
LinkedMultiValueMap<String, String> nativeHeaders = new LinkedMultiValueMap<>();
nativeHeaders.put("Foo-Id", singletonList("123"));
nativeHeaders.put("baz-id", singletonList("456"));
Map<String, Object> headers = new HashMap<>();
headers.put(NATIVE_HEADERS, nativeHeaders);
channel.send(MessageBuilder.createMessage("foo", new MessageHeaders(headers)));
Message<?> actualMessage = channel.receive();
assertThat(actualMessage.getHeaders()).isNotEmpty();
LinkedMultiValueMap<String, String> actualNativeHeaders = (LinkedMultiValueMap) actualMessage.getHeaders().get(NATIVE_HEADERS);
assertThat(actualNativeHeaders).isNotEmpty();
assertThat(actualNativeHeaders.get("not-propagated-header")).isNull();
assertThat(actualNativeHeaders.get("Foo-Id")).isEqualTo(singletonList("123"));
assertThat(actualNativeHeaders.get("baz-id")).isEqualTo(singletonList("456"));
}
public ChannelInterceptor producerSideOnly(ChannelInterceptor delegate) {
return new ChannelInterceptorAdapter() {
@Override