Skip to content

Commit a4e7f72

Browse files
committed
fix(rocketmq): log warning when FLAG or DELAY header is not numeric
RocketMQMessageConverterSupport#getAndWrapMessage swallowed any Integer.parseInt failure on the MQ_FLAG and DELAY_TIME_LEVEL message headers with catch (Exception ignored) {}, silently falling back to flag=0 and delayLevel=0. A producer that set DELAY to a misconfigured non-numeric value (e.g. via a SpEL expression that produced a String instead of an int) would have its delayed delivery silently turn into an immediate send with no log line and no way to know the header was malformed. Parse each header independently in a small parseHeaderAsIntOrDefault helper that warns, naming the offending header and echoing its raw value, when parsing fails. Parsing both headers in a single shared try block had two problems: a valid value on the header parsed first (e.g. a legitimate DELAY) was reported in the warning as having fallen back to 0 even though it was actually applied, and an exception on the header parsed first left the other, well-formed header unparsed and forced it to 0 as well. Independent parsing keeps every well-formed value, and the warning now names only the header that actually failed. Only the exception message is logged (not the full stack trace) to avoid flooding the logs when many messages carry the same bad header, and the catch is narrowed from Exception to NumberFormatException. Adds regression tests covering both the fallback behaviour (non-numeric DELAY and FLAG each stay at 0, conversion never propagates a parse exception) and the behaviour introduced here: the warning is emitted for a malformed header (asserted with OutputCaptureExtension), a valid delay level survives a malformed flag on the same message, and a valid flag survives a malformed delay level.
1 parent 4877f98 commit a4e7f72

2 files changed

Lines changed: 139 additions & 14 deletions

File tree

spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/support/RocketMQMessageConverterSupport.java

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
2727
import org.apache.rocketmq.common.message.MessageConst;
2828
import org.apache.rocketmq.common.message.MessageExt;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
2931

3032
import org.springframework.messaging.Message;
3133
import org.springframework.messaging.MessageHeaders;
@@ -40,6 +42,9 @@
4042
*/
4143
public final class RocketMQMessageConverterSupport {
4244

45+
private static final Logger log = LoggerFactory
46+
.getLogger(RocketMQMessageConverterSupport.class);
47+
4348
private RocketMQMessageConverterSupport() {
4449
}
4550

@@ -153,20 +158,15 @@ private static org.apache.rocketmq.common.message.Message getAndWrapMessage(
153158
}
154159
Object flagObj = headers.getOrDefault(Headers.FLAG,
155160
headers.get(toRocketHeaderKey(Headers.FLAG)));
156-
int flag = 0;
157-
int delayLevel = 0;
158-
try {
159-
flagObj = flagObj == null ? 0 : flagObj;
160-
Object delayLevelObj = headers.getOrDefault(
161-
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL,
162-
headers.get(toRocketHeaderKey(
163-
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL)));
164-
delayLevelObj = delayLevelObj == null ? 0 : delayLevelObj;
165-
delayLevel = Integer.parseInt(String.valueOf(delayLevelObj));
166-
flag = Integer.parseInt(String.valueOf(flagObj));
167-
}
168-
catch (Exception ignored) {
169-
}
161+
Object delayLevelObj = headers.getOrDefault(
162+
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL,
163+
headers.get(toRocketHeaderKey(
164+
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL)));
165+
// Parse each header independently so a malformed value in one never
166+
// forces the other, well-formed header to fall back to 0.
167+
int flag = parseHeaderAsIntOrDefault(Headers.FLAG, flagObj);
168+
int delayLevel = parseHeaderAsIntOrDefault(
169+
RocketMQConst.PROPERTY_DELAY_TIME_LEVEL, delayLevelObj);
170170
if (delayLevel > 0) {
171171
rocketMsg.setDelayTimeLevel(delayLevel);
172172
}
@@ -191,4 +191,30 @@ private static org.apache.rocketmq.common.message.Message getAndWrapMessage(
191191
return rocketMsg;
192192
}
193193

194+
/**
195+
* Parses a message header value as an {@code int}, falling back to {@code 0}
196+
* when the header is absent or not a valid integer. Each header is parsed on
197+
* its own so a malformed value in one header never drags a well-formed value
198+
* in another header down to the fallback, and the warning names only the
199+
* header that actually failed. Only the exception message is logged (no stack
200+
* trace) to avoid flooding the logs when many messages carry the same bad
201+
* header.
202+
* @param headerName the header name, used only for the warning message
203+
* @param rawValue the raw header value, may be {@code null}
204+
* @return the parsed value, or {@code 0} when absent or non-numeric
205+
*/
206+
private static int parseHeaderAsIntOrDefault(String headerName, Object rawValue) {
207+
if (rawValue == null) {
208+
return 0;
209+
}
210+
try {
211+
return Integer.parseInt(String.valueOf(rawValue));
212+
}
213+
catch (NumberFormatException e) {
214+
log.warn("Non-numeric '{}' header value [{}]; falling back to 0: {}",
215+
headerName, rawValue, e.getMessage());
216+
return 0;
217+
}
218+
}
219+
194220
}

spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/RocketMQMessageConverterSupportTest.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,24 @@
1616

1717
package com.alibaba.cloud.stream.binder.rocketmq;
1818

19+
import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst;
1920
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport;
2021
import org.apache.rocketmq.common.message.MessageConst;
2122
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
2224

25+
import org.springframework.boot.test.system.CapturedOutput;
26+
import org.springframework.boot.test.system.OutputCaptureExtension;
2327
import org.springframework.messaging.Message;
2428
import org.springframework.messaging.support.MessageBuilder;
2529

2630
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.assertThatCode;
2732

2833
/**
2934
* @author Sorie
3035
*/
36+
@ExtendWith(OutputCaptureExtension.class)
3137
public class RocketMQMessageConverterSupportTest {
3238

3339
@Test
@@ -44,4 +50,97 @@ public void convertMessage2MQBlankHeaderTest() {
4450
assertThat(testProp).isNull();
4551
assertThat(tagProp).isEqualTo("a");
4652
}
53+
54+
@Test
55+
public void nonNumericDelayTimeLevelHeaderFallsBackToZero() {
56+
Message<String> message = MessageBuilder.withPayload("msg")
57+
.setHeader(RocketMQConst.PROPERTY_DELAY_TIME_LEVEL, "not-a-number")
58+
.build();
59+
60+
org.apache.rocketmq.common.message.Message rkmqMsg =
61+
RocketMQMessageConverterSupport.convertMessage2MQ("topic", message);
62+
63+
assertThat(rkmqMsg.getDelayTimeLevel()).isEqualTo(0);
64+
assertThat(rkmqMsg.getFlag()).isEqualTo(0);
65+
}
66+
67+
@Test
68+
public void nonNumericFlagHeaderFallsBackToZero() {
69+
Message<String> message = MessageBuilder.withPayload("msg")
70+
.setHeader(RocketMQConst.Headers.FLAG, "not-a-number")
71+
.build();
72+
73+
org.apache.rocketmq.common.message.Message rkmqMsg =
74+
RocketMQMessageConverterSupport.convertMessage2MQ("topic", message);
75+
76+
assertThat(rkmqMsg.getFlag()).isEqualTo(0);
77+
}
78+
79+
@Test
80+
public void invalidNumericHeaderDoesNotPropagateException() {
81+
Message<String> message = MessageBuilder.withPayload("msg")
82+
.setHeader(RocketMQConst.PROPERTY_DELAY_TIME_LEVEL, "x")
83+
.setHeader(RocketMQConst.Headers.FLAG, "y")
84+
.build();
85+
86+
assertThatCode(() ->
87+
RocketMQMessageConverterSupport.convertMessage2MQ("topic", message))
88+
.doesNotThrowAnyException();
89+
}
90+
91+
@Test
92+
public void nonNumericFlagHeaderLogsWarning(CapturedOutput output) {
93+
Message<String> message = MessageBuilder.withPayload("msg")
94+
.setHeader(RocketMQConst.Headers.FLAG, "flag-not-a-number")
95+
.build();
96+
97+
RocketMQMessageConverterSupport.convertMessage2MQ("topic", message);
98+
99+
// The malformed header must no longer be swallowed silently: the warning
100+
// names the offending header and echoes its raw value.
101+
assertThat(output).contains(RocketMQConst.Headers.FLAG)
102+
.contains("flag-not-a-number");
103+
}
104+
105+
@Test
106+
public void validDelayLevelWithNonNumericFlagIsPreservedAndWarnsOnlyFlag(
107+
CapturedOutput output) {
108+
Message<String> message = MessageBuilder.withPayload("msg")
109+
.setHeader(RocketMQConst.PROPERTY_DELAY_TIME_LEVEL, "3")
110+
.setHeader(RocketMQConst.Headers.FLAG, "flag-bad")
111+
.build();
112+
113+
org.apache.rocketmq.common.message.Message rkmqMsg =
114+
RocketMQMessageConverterSupport.convertMessage2MQ("topic", message);
115+
116+
// A valid delay level must survive a malformed flag on the same message;
117+
// the two headers are parsed independently.
118+
assertThat(rkmqMsg.getDelayTimeLevel()).isEqualTo(3);
119+
assertThat(rkmqMsg.getFlag()).isEqualTo(0);
120+
// The warning names the flag header only; the valid delay level must not
121+
// be reported as having fallen back.
122+
assertThat(output).contains("flag-bad")
123+
.doesNotContain("'" + RocketMQConst.PROPERTY_DELAY_TIME_LEVEL + "'");
124+
}
125+
126+
@Test
127+
public void validFlagWithNonNumericDelayLevelIsPreservedAndWarnsOnlyDelayLevel(
128+
CapturedOutput output) {
129+
Message<String> message = MessageBuilder.withPayload("msg")
130+
.setHeader(RocketMQConst.PROPERTY_DELAY_TIME_LEVEL, "delay-bad")
131+
.setHeader(RocketMQConst.Headers.FLAG, "7")
132+
.build();
133+
134+
org.apache.rocketmq.common.message.Message rkmqMsg =
135+
RocketMQMessageConverterSupport.convertMessage2MQ("topic", message);
136+
137+
// A valid flag must survive a malformed delay level on the same message;
138+
// the two headers are parsed independently.
139+
assertThat(rkmqMsg.getFlag()).isEqualTo(7);
140+
assertThat(rkmqMsg.getDelayTimeLevel()).isEqualTo(0);
141+
// The warning names the delay-level header only; the valid flag must not
142+
// be reported as having fallen back.
143+
assertThat(output).contains("delay-bad")
144+
.doesNotContain("'" + RocketMQConst.Headers.FLAG + "'");
145+
}
47146
}

0 commit comments

Comments
 (0)