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 098e2ea4 fix(server): 422 on rejected cloud credentials and async file
logging (#2052)
098e2ea4 is described below
commit 098e2ea4426659ebd84a71b9d1e92755e312a54c
Author: lizhimins <[email protected]>
AuthorDate: Thu Aug 13 11:52:22 2026 +0800
fix(server): 422 on rejected cloud credentials and async file logging
(#2052)
Cloud providers now map OpenAPI authentication failures to 422 instead
of 401: HTTP 401 is reserved for Studio session auth and would make
the frontend log the user out. Logback gains an async rolling file
appender under logs/rocketmq-studio (1GB per file, history in
otherdays/, 10 periods retained) alongside the console. Also move H2
to test scope, silence the MyBatis-Plus banner, level dev logs to
INFO, and bypass IDE-injected JVM proxies in the metrics source test.
---
server/pom.xml | 2 +-
.../provider/alibaba/AliyunClientFactory.java | 4 +-
.../provider/tencent/TencentClientFactory.java | 4 +-
server/src/main/resources/application-dev.yml | 4 +-
server/src/main/resources/application.yml | 1 +
server/src/main/resources/logback-spring.xml | 17 ++++++++
.../metrics/MultiBackendMetricsSourceTest.java | 46 +++++++++++++++++++---
.../provider/alibaba/AliyunClientFactoryTest.java | 4 +-
8 files changed, 70 insertions(+), 12 deletions(-)
diff --git a/server/pom.xml b/server/pom.xml
index c9e1cc12..dbcc5604 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -80,7 +80,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
- <scope>runtime</scope>
+ <scope>test</scope>
</dependency>
<!-- RocketMQ Admin Tools (kept from trunk #694) -->
<dependency>
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
index bb068daf..ea6b1a4a 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactory.java
@@ -170,7 +170,9 @@ public class AliyunClientFactory {
int status = statusCode == null ? 0 : statusCode;
log.warn("Aliyun OpenAPI failure: status={}, errCode={}, message={}",
status, errCode, message);
if (status == 401 || "InvalidAccessKeyId".equals(errCode) ||
"SignatureDoesNotMatch".equals(errCode)) {
- return new BusinessException(401, "Cloud credential is invalid");
+ // 422, not 401: HTTP 401 is reserved for Studio session auth; the
frontend logs the
+ // user out and redirects on any 401, so a cloud-rejected
credential must use another code.
+ return new BusinessException(422, "Cloud credential is invalid");
}
if (status == 403) {
return new BusinessException(403, defaultIfBlank(message, "Aliyun
OpenAPI access denied"));
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
index 96a9b71c..dcf0923a 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/provider/tencent/TencentClientFactory.java
@@ -103,7 +103,9 @@ public class TencentClientFactory {
}
if (code != null && (code.contains("AuthFailure") ||
code.contains("InvalidCredential")
|| code.contains("InvalidSecretId") ||
code.contains("SignatureFailure"))) {
- return new BusinessException(401, "Cloud credential is invalid");
+ // 422, not 401: HTTP 401 is reserved for Studio session auth; the
frontend logs the
+ // user out and redirects on any 401, so a cloud-rejected
credential must use another code.
+ return new BusinessException(422, "Cloud credential is invalid");
}
if (code != null && (code.contains("NotFound") ||
code.contains("ResourceNotFound"))) {
return new BusinessException(404, defaultIfBlank(message, "Tencent
Cloud resource not found"));
diff --git a/server/src/main/resources/application-dev.yml
b/server/src/main/resources/application-dev.yml
index c6af6056..5b5ef1a8 100644
--- a/server/src/main/resources/application-dev.yml
+++ b/server/src/main/resources/application-dev.yml
@@ -16,5 +16,5 @@ spring:
logging:
level:
- org.apache.rocketmq.studio: DEBUG
- org.springframework.web: DEBUG
+ org.apache.rocketmq.studio: INFO
+ org.springframework.web: INFO
diff --git a/server/src/main/resources/application.yml
b/server/src/main/resources/application.yml
index 3e6d42d9..5b253cbe 100644
--- a/server/src/main/resources/application.yml
+++ b/server/src/main/resources/application.yml
@@ -17,6 +17,7 @@ mybatis-plus:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
+ banner: false
db-config:
table-prefix: rmq_
id-type: auto
diff --git a/server/src/main/resources/logback-spring.xml
b/server/src/main/resources/logback-spring.xml
index 274690af..6342b99e 100644
--- a/server/src/main/resources/logback-spring.xml
+++ b/server/src/main/resources/logback-spring.xml
@@ -7,7 +7,24 @@
<include
resource="org/springframework/boot/logging/logback/console-appender.xml"/>
+ <appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>logs/rocketmq-studio/studio.log</file>
+ <rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+
<fileNamePattern>logs/rocketmq-studio/otherdays/studio.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+ <maxFileSize>1GB</maxFileSize>
+ <maxHistory>10</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${FILE_LOG_PATTERN}</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
+ <appender-ref ref="FILE"/>
+ </appender>
+
<root level="INFO">
<appender-ref ref="CONSOLE"/>
+ <appender-ref ref="ASYNC_FILE"/>
</root>
</configuration>
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
index d2323455..74c69742 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/metrics/MultiBackendMetricsSourceTest.java
@@ -20,7 +20,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import org.apache.rocketmq.studio.model.MetricsDataSourceConfig;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -28,8 +30,17 @@ import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.web.client.RestClient;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
@@ -41,6 +52,32 @@ class MultiBackendMetricsSourceTest {
private final MetricsSourceFactory factory =
new MetricsSourceFactory(RestClient.builder(), new ObjectMapper());
+ private static ProxySelector originalProxySelector;
+
+ @BeforeAll
+ static void bypassJvmProxy() {
+ // The IDE (e.g. IDEA with a PAC proxy) may inject a proxy into the
test JVM. The embedded
+ // server is bound to a site-local address that is not in
http.nonProxyHosts, so the request
+ // would be routed through the proxy and time out. Force a direct
connection for this test.
+ originalProxySelector = ProxySelector.getDefault();
+ ProxySelector.setDefault(new ProxySelector() {
+ @Override
+ public List<Proxy> select(URI uri) {
+ return List.of(Proxy.NO_PROXY);
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress socketAddress,
IOException exception) {
+ // Nothing to do; the test never relies on a proxy.
+ }
+ });
+ }
+
+ @AfterAll
+ static void restoreJvmProxy() {
+ ProxySelector.setDefault(originalProxySelector);
+ }
+
@BeforeEach
void setUp() throws IOException {
java.net.InetAddress bindAddress = findSiteLocalAddress();
@@ -128,15 +165,14 @@ class MultiBackendMetricsSourceTest {
}
private static java.net.InetAddress findSiteLocalAddress() throws
java.net.SocketException {
- java.util.Enumeration<java.net.NetworkInterface> interfaces =
- java.net.NetworkInterface.getNetworkInterfaces();
+ Enumeration<NetworkInterface> interfaces =
NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
- java.net.NetworkInterface iface = interfaces.nextElement();
+ NetworkInterface iface = interfaces.nextElement();
if (!iface.isUp() || iface.isLoopback()) {
continue;
}
- for (java.net.InterfaceAddress address :
iface.getInterfaceAddresses()) {
- java.net.InetAddress inet = address.getAddress();
+ for (InterfaceAddress address : iface.getInterfaceAddresses()) {
+ InetAddress inet = address.getAddress();
if (inet instanceof java.net.Inet4Address
&& inet.isSiteLocalAddress()
&& !inet.isLoopbackAddress()
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
index 3f6c6312..4f0b707a 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/provider/alibaba/AliyunClientFactoryTest.java
@@ -185,7 +185,7 @@ class AliyunClientFactoryTest {
}
@Test
- void callShouldMapInvalidAccessKeyTo401Test() {
+ void callShouldMapInvalidAccessKeyTo422Test() {
AliyunClientFactory spy = Mockito.spy(factory);
doReturn(asyncClient).when(spy).client(anyString(), anyString());
PopServerException error = new PopServerException("bad key");
@@ -198,7 +198,7 @@ class AliyunClientFactoryTest {
ListRegionsRequest.builder().build())))
.isInstanceOf(BusinessException.class)
.extracting("code")
- .isEqualTo(401);
+ .isEqualTo(422);
}
@Test