Skip to content

Commit 40b21c1

Browse files
garyrussellartembilan
authored andcommitted
GH-1066: Protect against null Headers
Resolves #1066 Certain clients (e.g. mapR) that emulate the Kafka clients do not properly populate the `ConsumerRecord.headers()` field. Check for null before mapping; also check `timestampType`. **cherry-pick to 2.2.x, 2.1.x** # Conflicts: # spring-kafka/src/main/java/org/springframework/kafka/support/converter/MessageConverter.java # spring-kafka/src/main/java/org/springframework/kafka/support/converter/MessagingMessageConverter.java
1 parent 04f4db3 commit 40b21c1

File tree

5 files changed

+99
-9
lines changed

5 files changed

+99
-9
lines changed

spring-kafka/src/main/java/org/springframework/kafka/support/converter/BatchMessagingMessageConverter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public RecordMessageConverter getRecordMessageConverter() {
125125
@Override
126126
public Message<?> toMessage(List<ConsumerRecord<?, ?>> records, Acknowledgment acknowledgment,
127127
Consumer<?, ?> consumer, Type type) {
128+
128129
KafkaMessageHeaders kafkaMessageHeaders = new KafkaMessageHeaders(this.generateMessageId,
129130
this.generateTimestamp);
130131

@@ -167,9 +168,11 @@ public Message<?> toMessage(List<ConsumerRecord<?, ?>> records, Acknowledgment a
167168
topics.add(record.topic());
168169
partitions.add(record.partition());
169170
offsets.add(record.offset());
170-
timestampTypes.add(record.timestampType().name());
171+
if (record.timestampType() != null) {
172+
timestampTypes.add(record.timestampType().name());
173+
}
171174
timestamps.add(record.timestamp());
172-
if (this.headerMapper != null) {
175+
if (this.headerMapper != null && record.headers() != null) {
173176
Map<String, Object> converted = new HashMap<>();
174177
this.headerMapper.toHeaders(record.headers(), converted);
175178
convertedHeaders.add(converted);

spring-kafka/src/main/java/org/springframework/kafka/support/converter/MessageConverter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616

1717
package org.springframework.kafka.support.converter;
1818

19+
import java.util.Map;
20+
21+
import org.apache.kafka.clients.consumer.Consumer;
22+
23+
import org.springframework.kafka.support.Acknowledgment;
24+
import org.springframework.kafka.support.KafkaHeaders;
25+
import org.springframework.lang.Nullable;
26+
1927
/**
2028
* A top level interface for message converters.
2129
*
@@ -25,4 +33,23 @@
2533
*/
2634
public interface MessageConverter {
2735

36+
default void commonHeaders(Acknowledgment acknowledgment, Consumer<?, ?> consumer, Map<String, Object> rawHeaders,
37+
Object theKey, Object topic, Object partition, Object offset,
38+
@Nullable Object timestampType, Object timestamp) {
39+
40+
rawHeaders.put(KafkaHeaders.RECEIVED_MESSAGE_KEY, theKey);
41+
rawHeaders.put(KafkaHeaders.RECEIVED_TOPIC, topic);
42+
rawHeaders.put(KafkaHeaders.RECEIVED_PARTITION_ID, partition);
43+
rawHeaders.put(KafkaHeaders.OFFSET, offset);
44+
rawHeaders.put(KafkaHeaders.TIMESTAMP_TYPE, timestampType);
45+
rawHeaders.put(KafkaHeaders.RECEIVED_TIMESTAMP, timestamp);
46+
47+
if (acknowledgment != null) {
48+
rawHeaders.put(KafkaHeaders.ACKNOWLEDGMENT, acknowledgment);
49+
}
50+
if (consumer != null) {
51+
rawHeaders.put(KafkaHeaders.CONSUMER, consumer);
52+
}
53+
}
54+
2855
}

spring-kafka/src/main/java/org/springframework/kafka/support/converter/MessagingMessageConverter.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,12 @@ public void setHeaderMapper(KafkaHeaderMapper headerMapper) {
101101
@Override
102102
public Message<?> toMessage(ConsumerRecord<?, ?> record, Acknowledgment acknowledgment, Consumer<?, ?> consumer,
103103
Type type) {
104+
104105
KafkaMessageHeaders kafkaMessageHeaders = new KafkaMessageHeaders(this.generateMessageId,
105106
this.generateTimestamp);
106107

107108
Map<String, Object> rawHeaders = kafkaMessageHeaders.getRawHeaders();
108-
if (this.headerMapper != null) {
109+
if (this.headerMapper != null && record.headers() != null) {
109110
this.headerMapper.toHeaders(record.headers(), rawHeaders);
110111
}
111112
else {
@@ -117,12 +118,9 @@ public Message<?> toMessage(ConsumerRecord<?, ?> record, Acknowledgment acknowle
117118
}
118119
rawHeaders.put(KafkaHeaders.NATIVE_HEADERS, record.headers());
119120
}
120-
rawHeaders.put(KafkaHeaders.RECEIVED_MESSAGE_KEY, record.key());
121-
rawHeaders.put(KafkaHeaders.RECEIVED_TOPIC, record.topic());
122-
rawHeaders.put(KafkaHeaders.RECEIVED_PARTITION_ID, record.partition());
123-
rawHeaders.put(KafkaHeaders.OFFSET, record.offset());
124-
rawHeaders.put(KafkaHeaders.TIMESTAMP_TYPE, record.timestampType().name());
125-
rawHeaders.put(KafkaHeaders.RECEIVED_TIMESTAMP, record.timestamp());
121+
String ttName = record.timestampType() != null ? record.timestampType().name() : null;
122+
commonHeaders(acknowledgment, consumer, rawHeaders, record.key(), record.topic(), record.partition(),
123+
record.offset(), ttName, record.timestamp());
126124

127125
if (acknowledgment != null) {
128126
rawHeaders.put(KafkaHeaders.ACKNOWLEDGMENT, acknowledgment);

spring-kafka/src/test/java/org/springframework/kafka/support/converter/BatchMessageConverterTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.util.ArrayList;
2323
import java.util.Arrays;
24+
import java.util.Collections;
2425
import java.util.Iterator;
2526
import java.util.List;
2627
import java.util.Map;
@@ -112,4 +113,18 @@ private MessageHeaders testGuts(BatchMessageConverter batchMessageConverter) {
112113
return headers;
113114
}
114115

116+
@SuppressWarnings("unchecked")
117+
@Test
118+
public void missingHeaders() {
119+
BatchMessageConverter converter = new BatchMessagingMessageConverter();
120+
Headers nullHeaders = null;
121+
ConsumerRecord<String, String> record = new ConsumerRecord<>("foo", 1, 42, -1L, null, 0L, 0, 0, "bar", "baz",
122+
nullHeaders);
123+
List<ConsumerRecord<?, ?>> records = Collections.singletonList(record);
124+
Message<?> message = converter.toMessage(records, null, null, null);
125+
assertThat(((List<String>) message.getPayload())).contains("baz");
126+
assertThat(message.getHeaders().get(KafkaHeaders.RECEIVED_TOPIC, List.class)).contains("foo");
127+
assertThat(message.getHeaders().get(KafkaHeaders.RECEIVED_MESSAGE_KEY, List.class)).contains("bar");
128+
}
129+
115130
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.kafka.support.converter;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.apache.kafka.clients.consumer.ConsumerRecord;
22+
import org.apache.kafka.common.header.Headers;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.kafka.support.KafkaHeaders;
26+
import org.springframework.messaging.Message;
27+
28+
/**
29+
* @author Gary Russell
30+
* @since 2.1.13
31+
*
32+
*/
33+
public class MessagingMessageConverterTests {
34+
35+
@Test
36+
void missingHeaders() {
37+
MessagingMessageConverter converter = new MessagingMessageConverter();
38+
Headers nullHeaders = null;
39+
ConsumerRecord<String, String> record = new ConsumerRecord<>("foo", 1, 42, -1L, null, 0L, 0, 0, "bar", "baz",
40+
nullHeaders);
41+
Message<?> message = converter.toMessage(record, null, null, null);
42+
assertThat(message.getPayload()).isEqualTo("baz");
43+
assertThat(message.getHeaders().get(KafkaHeaders.RECEIVED_TOPIC)).isEqualTo("foo");
44+
assertThat(message.getHeaders().get(KafkaHeaders.RECEIVED_MESSAGE_KEY)).isEqualTo("bar");
45+
}
46+
47+
}

0 commit comments

Comments
 (0)