Skip to content

Commit fd8be3e

Browse files
PRAHLAD09-devolegz
authored andcommitted
GH-3211: Restore timestamp header on consumed messages
Resolves #3245
1 parent d08baf4 commit fd8be3e

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

  • core
    • spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/function
    • spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function

core/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/function/HeaderTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,24 @@ void checkMessageWrappedFunctionalConsumer() {
129129
assertThat(headers.get(MessageHeaders.CONTENT_TYPE)).isEqualTo("application/json");
130130
}
131131

132+
@Test
133+
void timestampHeaderIsPresentOnConsumedMessage() {
134+
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
135+
TestChannelBinderConfiguration.getCompleteConfiguration(FunctionUpperCaseConfiguration.class))
136+
.web(WebApplicationType.NONE)
137+
.run("--spring.jmx.enabled=false",
138+
"--spring.cloud.function.definition=uppercase")) {
139+
140+
InputDestination input = context.getBean(InputDestination.class);
141+
input.send(new GenericMessage<>("hello".getBytes()), "uppercase-in-0");
142+
143+
OutputDestination output = context.getBean(OutputDestination.class);
144+
Message<byte[]> result = output.receive(1000, "uppercase-out-0");
145+
146+
assertThat(result.getHeaders().getTimestamp()).isNotNull();
147+
}
148+
}
149+
132150
@EnableAutoConfiguration
133151
public static class EmptyConfiguration {
134152

core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
import org.springframework.messaging.MessagingException;
114114
import org.springframework.messaging.SubscribableChannel;
115115
import org.springframework.messaging.support.ChannelInterceptor;
116+
import org.springframework.messaging.support.GenericMessage;
116117
import org.springframework.scheduling.TaskScheduler;
117118
import org.springframework.scheduling.Trigger;
118119
import org.springframework.scheduling.support.CronTrigger;
@@ -409,12 +410,19 @@ private <T> Message<T> wrapToMessageIfNecessary(T value) {
409410
}
410411

411412
private static <P> Message<P> sanitize(Message<P> inputMessage) {
412-
return MessageBuilder
413+
Message<P> sanitized = MessageBuilder
413414
.fromMessage(inputMessage)
414415
.removeHeader("spring.cloud.stream.sendto.destination")
415-
// .setHeader(MessageUtils.SOURCE_TYPE, inputMessage.getHeaders().get(MessageUtils.TARGET_PROTOCOL))
416-
// .removeHeader(MessageUtils.TARGET_PROTOCOL)
417416
.build();
417+
if (sanitized == inputMessage) {
418+
// MessageBuilder.build() returns the same instance when no header was
419+
// actually modified (fast-path in BaseMessageBuilder), which skips the
420+
// GenericMessage constructor and therefore skips stamping a "timestamp"
421+
// header. Force a fresh GenericMessage so the header is always present,
422+
// restoring pre-4.3.3 behavior. See GH-3211.
423+
sanitized = new GenericMessage<>(inputMessage.getPayload(), inputMessage.getHeaders());
424+
}
425+
return sanitized;
418426
}
419427

420428
private static class FunctionToDestinationBinder implements InitializingBean, ApplicationContextAware {

0 commit comments

Comments
 (0)