This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 1fdfe6cbe fix(aliyun): label a binary message body BASE64 instead of
TEXT (#4857)
1fdfe6cbe is described below
commit 1fdfe6cbec859b882adc688cbfd0fcdf85644566
Author: Wang1rrr <[email protected]>
AuthorDate: Thu Sep 24 18:18:49 2026 +0800
fix(aliyun): label a binary message body BASE64 instead of TEXT (#4857)
fix(aliyun): label a binary message body BASE64 instead of TEXT
AliyunConverters.toMessageRecord published bodyEncoding="TEXT", a value
docs/api-spec.md does not define and no other provider emits, and it put
the raw Base64 into body while doing so. The cause is tryBase64Decode
collapsing three outcomes into one null: no body, Base64 of a binary
payload, and a body that is not Base64 at all.
Replace it with a value/encoding pair that keeps them apart, matching the
shape RocketMQMessageProvider.displayBody already uses:
no body -> null / null
Base64 of binary -> the Base64 / "BASE64"
not Base64 -> the raw string / "UTF-8"
Base64 of text -> the decoded text / "UTF-8"
MessageItem and MessageQueryOutput forward the field verbatim and the
tool schema declares it as a bare string, so rmq.message.query* was
telling the model that a Base64 payload was literal text for every binary
message on an Aliyun instance.
---
.../studio/provider/alibaba/AliyunConverters.java | 31 +++++---
.../alibaba/AliyunConvertersBodyEncodingTest.java | 90 ++++++++++++++++++++++
.../alibaba/AliyunInstanceProviderTest.java | 4 +-
3 files changed, 114 insertions(+), 11 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
index e94d0fd7a..8209b18fc 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConverters.java
@@ -232,8 +232,7 @@ final class AliyunConverters {
}
static MessageRecordVO toMessageRecord(ListMessagesResponseBody.List data)
{
- String rawBody = data.getBody();
- String decodedBody = tryBase64Decode(rawBody);
+ Body body = decodeBody(data.getBody());
MessageRecordVO.MessageRecordVOBuilder builder =
MessageRecordVO.builder()
.msgId(data.getMessageId())
.topic(data.getTopicName())
@@ -244,10 +243,8 @@ final class AliyunConverters {
.storeTime(parseTimeMillis(data.getStoreTime()))
.properties(data.getUserProperties())
.size(data.getBodySize() == null ? 0 : data.getBodySize());
- if (decodedBody != null) {
- builder.body(decodedBody).bodyEncoding("UTF-8");
- } else {
- builder.body(rawBody).bodyEncoding("TEXT");
+ if (body != null) {
+ builder.body(body.value()).bodyEncoding(body.encoding());
}
return builder.build();
}
@@ -370,7 +367,17 @@ final class AliyunConverters {
LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
ALIYUN_TIME_ZONE));
}
- static String tryBase64Decode(String raw) {
+ /**
+ * ListMessages hands the body over Base64-encoded. Resolve it to the
value/encoding pair the
+ * shared {@code MessageRecordVO} contract publishes - {@code
docs/api-spec.md} documents
+ * {@code UTF-8} / {@code BASE64}, and {@code
RocketMQMessageProvider.displayBody} produces
+ * exactly those two. A payload that decodes to text is returned as text;
a payload that does
+ * not is binary, so the Base64 the API gave us is its faithful display
form and is labelled
+ * {@code BASE64}. A body that is not Base64 in the first place is already
literal text and
+ * keeps its own value. No body yields no pair, leaving both fields null
the way the Apache and
+ * Tencent providers do.
+ */
+ private static Body decodeBody(String raw) {
if (raw == null || raw.isBlank()) {
return null;
}
@@ -378,19 +385,23 @@ final class AliyunConverters {
try {
bytes = Base64.getDecoder().decode(raw);
} catch (IllegalArgumentException ignored) {
- return null;
+ return new Body(raw, "UTF-8");
}
try {
- return StandardCharsets.UTF_8.newDecoder()
+ String text = StandardCharsets.UTF_8.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(bytes))
.toString();
+ return new Body(text, "UTF-8");
} catch (CharacterCodingException ignored) {
- return null;
+ return new Body(raw, "BASE64");
}
}
+ private record Body(String value, String encoding) {
+ }
+
private static String joinMessageKeys(List<String> keys) {
return keys == null ? null : joinParts(" ",
keys.toArray(String[]::new));
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConvertersBodyEncodingTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConvertersBodyEncodingTest.java
new file mode 100644
index 000000000..be7145463
--- /dev/null
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunConvertersBodyEncodingTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.studio.provider.alibaba;
+
+import com.aliyun.sdk.service.rocketmq20220801.models.ListMessagesResponseBody;
+import org.apache.rocketmq.studio.instance.message.MessageRecordVO;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Pins the {@code bodyEncoding} vocabulary of the Aliyun message converter to
the one the rest of
+ * Studio publishes: {@code UTF-8} for text, {@code BASE64} for a payload that
is not text, and
+ * {@code null} when the API returned no body at all.
+ */
+class AliyunConvertersBodyEncodingTest {
+
+ private static MessageRecordVO recordWithBody(String body) {
+ return
AliyunConverters.toMessageRecord(ListMessagesResponseBody.List.builder()
+ .messageId("msg-1")
+ .body(body)
+ .build());
+ }
+
+ @Test
+ void shouldReportUtf8WhenTheBase64BodyDecodesToText() {
+ String encoded = Base64.getEncoder()
+
.encodeToString("{\"orderId\":42}".getBytes(StandardCharsets.UTF_8));
+
+ MessageRecordVO record = recordWithBody(encoded);
+
+ assertThat(record.getBody()).isEqualTo("{\"orderId\":42}");
+ assertThat(record.getBodyEncoding()).isEqualTo("UTF-8");
+ }
+
+ @Test
+ void shouldReportBase64WhenThePayloadIsBinary() {
+ // 0xFF 0xFE is not valid UTF-8, so the decoder reports a coding error
and the Base64
+ // the OpenAPI returned is the only faithful way to show the payload.
+ String encoded = Base64.getEncoder().encodeToString(new byte[] {(byte)
0xFF, (byte) 0xFE});
+
+ MessageRecordVO record = recordWithBody(encoded);
+
+ assertThat(record.getBody()).isEqualTo(encoded);
+ assertThat(record.getBodyEncoding()).isEqualTo("BASE64");
+ }
+
+ @Test
+ void shouldKeepLiteralTextWhenTheBodyIsNotBase64() {
+ MessageRecordVO record = recordWithBody("{\"orderId\":42}");
+
+ assertThat(record.getBody()).isEqualTo("{\"orderId\":42}");
+ assertThat(record.getBodyEncoding()).isEqualTo("UTF-8");
+ }
+
+ @Test
+ void shouldReportNoEncodingWhenTheApiReturnedNoBody() {
+ assertThat(recordWithBody(null).getBodyEncoding()).isNull();
+ assertThat(recordWithBody(null).getBody()).isNull();
+ assertThat(recordWithBody(" ").getBodyEncoding()).isNull();
+ }
+
+ @Test
+ void shouldNeverEmitAnEncodingOutsideThePublishedVocabulary() {
+ String text =
Base64.getEncoder().encodeToString("plain".getBytes(StandardCharsets.UTF_8));
+ String binary = Base64.getEncoder().encodeToString(new byte[] {(byte)
0xFF});
+
+ for (String body : Arrays.asList(text, binary, "not base64 at all!!",
"", null)) {
+ assertThat(recordWithBody(body).getBodyEncoding()).isIn("UTF-8",
"BASE64", null);
+ }
+ }
+}
\ No newline at end of file
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
index 2edbd0654..a3a167f92 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunInstanceProviderTest.java
@@ -540,8 +540,10 @@ class AliyunInstanceProviderTest {
assertThat(first.getBornHost()).isEqualTo("10.0.0.1");
assertThat(first.getProperties()).containsEntry("a", "b");
MessageRecordVO second = records.get(1);
+ // "{}" is not Base64, so the API handed back literal text; the shared
contract has no
+ // "TEXT" label and UTF-8 is the one that describes a plain string
body.
assertThat(second.getBody()).isEqualTo("{}");
- assertThat(second.getBodyEncoding()).isEqualTo("TEXT");
+ assertThat(second.getBodyEncoding()).isEqualTo("UTF-8");
List<MessageRecordVO> filtered =
provider.queryMessages(STUDIO_INSTANCE_ID, "topic-a", null,
"tagB", null, null, null);