This is an automated email from the ASF dual-hosted git repository.
jianbin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 7b13e00f1c bugfix: fix IllegalArgumentException when GET request has
request body (#8035)
7b13e00f1c is described below
commit 7b13e00f1c94f5d99b83447b689086c7bbd7174c
Author: Jiangke Wu <[email protected]>
AuthorDate: Thu Apr 2 14:33:05 2026 +0800
bugfix: fix IllegalArgumentException when GET request has request body
(#8035)
---
changes/en-us/2.x.md | 1 +
changes/zh-cn/2.x.md | 1 +
.../namingserver/filter/ConsoleRemotingFilter.java | 158 ++++++++++++--
.../filter/ConsoleRemotingFilterTest.java | 243 +++++++++++++++++++++
4 files changed, 387 insertions(+), 16 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index bfa311bcab..d2685150b1 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -37,6 +37,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#7956](https://github.com/apache/incubator-seata/pull/7956)] fix empty
jacoco report on local when jdk above 17
- [[#7965](https://github.com/apache/incubator-seata/pull/7965)] fix the issue
where different element order in two lists causes failure to set the index as
the primary key
- [[#7992](https://github.com/apache/incubator-seata/pull/7992)] fix report
branch transaction status without setting branch type
+- [[#8035](https://github.com/apache/incubator-seata/pull/8035)] fix
IllegalArgumentException when GET request has request body
### optimize:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 4091e9fb42..c969ea2c87 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -38,6 +38,7 @@
- [[#7956](https://github.com/apache/incubator-seata/pull/7956)]
修复本地JDK17以上jacoco报告为空的问题
- [[#7965](https://github.com/apache/incubator-seata/pull/7965)] 修复在 dm 和
kingbase 中当没有将索引设置为主键索引时出现的问题
- [[#7992](https://github.com/apache/incubator-seata/pull/7992)]
修复报告分支事务状态时没有设置分支类型
+- [[#8035](https://github.com/apache/incubator-seata/pull/8035)]
修复GET请求有请求体的时候出现 IllegalArgumentException
### optimize:
diff --git
a/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
b/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
index 2a7357468d..b4c85a62fc 100644
---
a/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
+++
b/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
@@ -40,9 +40,10 @@ import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
+import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
@@ -57,13 +58,64 @@ public class ConsoleRemotingFilter implements Filter {
private final Pattern urlPattern = Pattern.compile(CONSOLE_PATTERN);
- private final Logger logger =
LoggerFactory.getLogger(ConsoleRemotingFilter.class);
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ConsoleRemotingFilter.class);
public ConsoleRemotingFilter(NamingManager namingManager, RestTemplate
restTemplate) {
this.namingManager = namingManager;
this.restTemplate = restTemplate;
}
+ /**
+ * Check whether the proxied Content-Type is safe (will not be rendered as
+ * HTML / XML by the browser). Only allow known-safe MIME types through
+ * (allowlist approach); everything else is replaced with
+ * {@code application/json}.
+ */
+ private static boolean isSafeContentType(String contentType) {
+ if (contentType == null) {
+ return false;
+ }
+ String lower = contentType.toLowerCase(Locale.ROOT);
+ // Extract the primary MIME type (ignore parameters such as charset)
+ int semicolonIdx = lower.indexOf(';');
+ String mimeType = (semicolonIdx >= 0 ? lower.substring(0,
semicolonIdx) : lower).trim();
+ return "application/json".equals(mimeType)
+ || "text/plain".equals(mimeType)
+ || "application/octet-stream".equals(mimeType);
+ }
+
+ /**
+ * Validate that the given byte array looks like well-formed JSON
+ * (starts with '{' or '[' after trimming leading whitespace).
+ * This is a lightweight sanity check to prevent forwarding
+ * arbitrary HTML / script payloads disguised as JSON.
+ */
+ private static boolean looksLikeJson(byte[] body) {
+ if (body == null || body.length == 0) {
+ return true;
+ }
+ int i = 0;
+ // Skip optional UTF-8 BOM (0xEF, 0xBB, 0xBF)
+ if (body.length >= 3
+ && (body[0] & 0xFF) == 0xEF
+ && (body[1] & 0xFF) == 0xBB
+ && (body[2] & 0xFF) == 0xBF) {
+ i = 3;
+ }
+ // skip leading whitespace (including Unicode NBSP / BOM that survived
as whitespace)
+ while (i < body.length && (body[i] == ' ' || body[i] == '\t'
+ || body[i] == '\r' || body[i] == '\n')) {
+ i++;
+ }
+ if (i >= body.length) {
+ return true;
+ }
+ byte first = body[i];
+ return first == '{' || first == '[' || first == '"'
+ || first == 't' || first == 'f' || first == 'n'
+ || (first >= '0' && first <= '9') || first == '-';
+ }
+
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain)
throws IOException, ServletException {
@@ -99,42 +151,116 @@ public class ConsoleRemotingFilter implements Filter {
+ request.getRequestURI()
+ (request.getQueryString() != null ? "?"
+ request.getQueryString() : "");
- // Copy headers from the original request
+ // Copy headers from the original request,
stripping hop-by-hop
+ // headers (RFC 7230 §6.1) and Host (so the client
library sets
+ // the correct value for the upstream target).
HttpHeaders headers = new HttpHeaders();
if (node.getRole() == ClusterRole.LEADER) {
headers.add(RAFT_GROUP_HEADER, node.getUnit());
}
Collections.list(request.getHeaderNames())
- .forEach(headerName ->
headers.add(headerName, request.getHeader(headerName)));
+ .forEach(headerName -> {
+ if
(!HttpHeaders.HOST.equalsIgnoreCase(headerName)
+ &&
!HttpHeaders.CONNECTION.equalsIgnoreCase(headerName)
+ &&
!"Keep-Alive".equalsIgnoreCase(headerName)
+ &&
!HttpHeaders.PROXY_AUTHENTICATE.equalsIgnoreCase(headerName)
+ &&
!HttpHeaders.PROXY_AUTHORIZATION.equalsIgnoreCase(headerName)
+ &&
!HttpHeaders.TE.equalsIgnoreCase(headerName)
+ &&
!HttpHeaders.TRAILER.equalsIgnoreCase(headerName)
+ &&
!HttpHeaders.UPGRADE.equalsIgnoreCase(headerName)) {
+ headers.add(headerName,
request.getHeader(headerName));
+ }
+ });
// Create the HttpEntity with headers and body
- HttpEntity<byte[]> httpEntity = new
HttpEntity<>(request.getCachedBody(), headers);
HttpMethod httpMethod;
try {
httpMethod =
HttpMethod.valueOf(request.getMethod());
} catch (IllegalArgumentException ex) {
- logger.error("Unsupported HTTP method: {}",
request.getMethod(), ex);
+ LOGGER.error("Unsupported HTTP method: {}",
request.getMethod(), ex);
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
+
+ // GET/HEAD methods should not have a body; other
methods may include a body as needed.
+ HttpEntity<byte[]> httpEntity;
+ if (HttpMethod.GET.equals(httpMethod) ||
HttpMethod.HEAD.equals(httpMethod)) {
+ headers.remove(HttpHeaders.CONTENT_LENGTH);
+ headers.remove(HttpHeaders.TRANSFER_ENCODING);
+ // headers-only
+ httpEntity = new HttpEntity<>(headers);
+ } else {
+ byte[] body = request.getCachedBody();
+ if (body == null || body.length == 0) {
+ headers.remove(HttpHeaders.CONTENT_LENGTH);
+
headers.remove(HttpHeaders.TRANSFER_ENCODING);
+ // headers-only for empty body
+ httpEntity = new HttpEntity<>(headers);
+ } else {
+ // Remove potentially stale
length/transfer headers and let the client recompute them
+ headers.remove(HttpHeaders.CONTENT_LENGTH);
+
headers.remove(HttpHeaders.TRANSFER_ENCODING);
+ httpEntity = new HttpEntity<>(body,
headers);
+ }
+ }
+
try {
- ResponseEntity<byte[]> responseEntity =
restTemplate.exchange(
- URI.create(targetUrl), httpMethod,
httpEntity, byte[].class);
+ ResponseEntity<byte[]> responseEntity =
restTemplate.exchange(URI.create(targetUrl), httpMethod, httpEntity,
byte[].class);
+ //Copy headers from proxied response, skipping
hop-by-hop and headers we manage ourselves to mitigate
+ // security risks from Content-Type
manipulation
responseEntity.getHeaders().forEach((key,
value) -> {
- value.forEach(v -> response.addHeader(key,
v));
+ if
(!HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(key)
+ &&
!HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(key)
+ &&
!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(key)
+ &&
!"X-Content-Type-Options".equalsIgnoreCase(key)
+ &&
!HttpHeaders.CONNECTION.equalsIgnoreCase(key)
+ &&
!"Keep-Alive".equalsIgnoreCase(key)
+ &&
!HttpHeaders.PROXY_AUTHENTICATE.equalsIgnoreCase(key)
+ &&
!HttpHeaders.PROXY_AUTHORIZATION.equalsIgnoreCase(key)
+ &&
!HttpHeaders.TE.equalsIgnoreCase(key)
+ &&
!HttpHeaders.TRAILER.equalsIgnoreCase(key)
+ &&
!HttpHeaders.UPGRADE.equalsIgnoreCase(key)) {
+ value.forEach(v ->
response.addHeader(key, v));
+ }
});
- response.setStatus(
-
responseEntity.getStatusCode().value());
-
Optional.ofNullable(responseEntity.getBody()).ifPresent(body -> {
+ // Force a safe Content-Type: reject HTML/XML
types that could
+ // execute scripts; fall back to
application/json
+ String proxiedContentType =
responseEntity.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
+ String safeContentType;
+ if (isSafeContentType(proxiedContentType)) {
+ safeContentType = proxiedContentType;
+ } else {
+ safeContentType =
"application/json;charset=UTF-8";
+ }
+ response.setContentType(safeContentType);
+ response.setHeader("X-Content-Type-Options",
"nosniff");
+
response.setStatus(responseEntity.getStatusCode().value());
+ byte[] responseBody = responseEntity.getBody();
+ // HEAD responses must not include a message
body (RFC 7231 §4.3.2)
+ if (!HttpMethod.HEAD.equals(httpMethod)
+ && responseBody != null &&
responseBody.length > 0) {
+ // For JSON content type, validate that
the body actually looks
+ // like JSON to prevent XSS via crafted
upstream responses
+ if
(safeContentType.toLowerCase(Locale.ROOT).contains("application/json")
+ && !looksLikeJson(responseBody)) {
+ LOGGER.warn("Upstream returned
non-JSON body for Content-Type {}, replacing with error response",
safeContentType);
+
response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
+
response.setContentType("application/json;charset=UTF-8");
+ responseBody = "{\"error\":\"Upstream
returned invalid response body\"}"
+
.getBytes(StandardCharsets.UTF_8);
+ }
try (ServletOutputStream outputStream =
response.getOutputStream()) {
- outputStream.write(body);
+ outputStream.write(responseBody);
outputStream.flush();
} catch (IOException e) {
- logger.error(e.getMessage(), e);
+ // Client likely disconnected (broken
pipe); log at debug
+ // level and do NOT attempt sendError
– the response may
+ // already be committed.
+ LOGGER.debug("Failed to write proxy
response body (client disconnect?): {}", e.getMessage(), e);
}
- });
+ }
} catch (Exception ex) {
- logger.error(ex.getMessage(), ex);
+ LOGGER.error(ex.getMessage(), ex);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return;
diff --git
a/namingserver/src/test/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilterTest.java
b/namingserver/src/test/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilterTest.java
new file mode 100644
index 0000000000..0efa2da41c
--- /dev/null
+++
b/namingserver/src/test/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilterTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.seata.namingserver.filter;
+
+import jakarta.servlet.FilterChain;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.metadata.namingserver.NamingServerNode;
+import org.apache.seata.namingserver.manager.NamingManager;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.client.RestTemplate;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link ConsoleRemotingFilter}.
+ * <p>
+ * Covers the GET/HEAD body-stripping regression (OkHttp
IllegalArgumentException)
+ * and other proxy-forwarding behavior.
+ */
+class ConsoleRemotingFilterTest {
+
+ private NamingManager namingManager;
+ private RestTemplate restTemplate;
+ private ConsoleRemotingFilter filter;
+ private FilterChain filterChain;
+
+ private static final String NAMESPACE = "public";
+ private static final String CLUSTER = "default";
+ private static final String TARGET_HOST = "127.0.0.1";
+ private static final int TARGET_PORT = 7091;
+
+ @BeforeEach
+ void setUp() {
+ namingManager = mock(NamingManager.class);
+ restTemplate = mock(RestTemplate.class);
+ filterChain = mock(FilterChain.class);
+ filter = new ConsoleRemotingFilter(namingManager, restTemplate);
+
+ // Set up a NamingServerNode with a control endpoint
+ NamingServerNode node = new NamingServerNode();
+ node.setControl(new Node.Endpoint(TARGET_HOST, TARGET_PORT, "http"));
+
+ when(namingManager.getInstances(NAMESPACE, CLUSTER))
+ .thenReturn(Collections.singletonList(node));
+ }
+
+ /**
+ * Regression test: a GET request with a non-empty body should NOT forward
+ * the body to the upstream server (to avoid OkHttp's
IllegalArgumentException).
+ * The body, Content-Length, and Transfer-Encoding headers must be
stripped.
+ */
+ @Test
+ void getRequestWithBodyShouldStripBody() throws Exception {
+ // Prepare a GET request with a body (some clients/frameworks may
attach one)
+ MockHttpServletRequest request = createConsoleRequest("GET");
+
request.setContent("{\"key\":\"value\"}".getBytes(StandardCharsets.UTF_8));
+ request.addHeader(HttpHeaders.CONTENT_LENGTH, "15");
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ // Stub RestTemplate to return a successful JSON response
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.set(HttpHeaders.CONTENT_TYPE,
"application/json;charset=UTF-8");
+ ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+ "{\"result\":\"ok\"}".getBytes(StandardCharsets.UTF_8),
+ responseHeaders,
+ HttpStatus.OK);
+
+ when(restTemplate.exchange(any(URI.class), eq(HttpMethod.GET),
any(HttpEntity.class), eq(byte[].class)))
+ .thenReturn(upstreamResponse);
+
+ filter.doFilter(request, response, filterChain);
+
+ // Capture the HttpEntity sent to RestTemplate
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<HttpEntity<byte[]>> entityCaptor =
ArgumentCaptor.forClass(HttpEntity.class);
+ verify(restTemplate).exchange(any(URI.class), eq(HttpMethod.GET),
entityCaptor.capture(), eq(byte[].class));
+
+ HttpEntity<byte[]> capturedEntity = entityCaptor.getValue();
+ // Body must be null (stripped for GET)
+ assertNull(capturedEntity.getBody(), "GET request body should be
stripped (null)");
+ // Content-Length and Transfer-Encoding headers must not be forwarded
+ assertNull(capturedEntity.getHeaders().get(HttpHeaders.CONTENT_LENGTH),
+ "Content-Length header should be removed for GET");
+
assertNull(capturedEntity.getHeaders().get(HttpHeaders.TRANSFER_ENCODING),
+ "Transfer-Encoding header should be removed for GET");
+
+ // Verify filterChain was NOT invoked (proxied)
+ verify(filterChain, never()).doFilter(any(), any());
+ assertEquals(200, response.getStatus());
+ }
+
+ /**
+ * HEAD request should also strip the body, same as GET.
+ */
+ @Test
+ void headRequestShouldStripBody() throws Exception {
+ MockHttpServletRequest request = createConsoleRequest("HEAD");
+ request.setContent("some body".getBytes(StandardCharsets.UTF_8));
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json");
+ ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+ null, responseHeaders, HttpStatus.OK);
+
+ when(restTemplate.exchange(any(URI.class), eq(HttpMethod.HEAD),
any(HttpEntity.class), eq(byte[].class)))
+ .thenReturn(upstreamResponse);
+
+ filter.doFilter(request, response, filterChain);
+
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<HttpEntity<byte[]>> entityCaptor =
ArgumentCaptor.forClass(HttpEntity.class);
+ verify(restTemplate).exchange(any(URI.class), eq(HttpMethod.HEAD),
entityCaptor.capture(), eq(byte[].class));
+
+ assertNull(entityCaptor.getValue().getBody(), "HEAD request body
should be stripped (null)");
+ verify(filterChain, never()).doFilter(any(), any());
+ }
+
+ /**
+ * POST request should forward the body as-is.
+ */
+ @Test
+ void postRequestShouldForwardBody() throws Exception {
+ byte[] bodyBytes =
"{\"data\":\"test\"}".getBytes(StandardCharsets.UTF_8);
+ MockHttpServletRequest request = createConsoleRequest("POST");
+ request.setContent(bodyBytes);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.set(HttpHeaders.CONTENT_TYPE,
"application/json;charset=UTF-8");
+ ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+ "{\"result\":\"created\"}".getBytes(StandardCharsets.UTF_8),
+ responseHeaders,
+ HttpStatus.OK);
+
+ when(restTemplate.exchange(any(URI.class), eq(HttpMethod.POST),
any(HttpEntity.class), eq(byte[].class)))
+ .thenReturn(upstreamResponse);
+
+ filter.doFilter(request, response, filterChain);
+
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor<HttpEntity<byte[]>> entityCaptor =
ArgumentCaptor.forClass(HttpEntity.class);
+ verify(restTemplate).exchange(any(URI.class), eq(HttpMethod.POST),
entityCaptor.capture(), eq(byte[].class));
+
+ byte[] capturedBody = entityCaptor.getValue().getBody();
+ assertNotNull(capturedBody, "POST body should not be null");
+ assertEquals(new String(bodyBytes, StandardCharsets.UTF_8),
+ new String(capturedBody, StandardCharsets.UTF_8),
+ "POST request body should be forwarded as-is");
+ }
+
+ /**
+ * Non-matching URL should pass through the filter chain without proxying.
+ */
+ @Test
+ void nonConsoleUrlShouldPassThrough() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest("GET",
"/api/v1/other/endpoint");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ filter.doFilter(request, response, filterChain);
+
+ verify(filterChain).doFilter(any(), any());
+ verify(restTemplate, never()).exchange(any(URI.class), any(),
any(HttpEntity.class), eq(byte[].class));
+ }
+
+ /**
+ * Upstream returning an HTML body with application/json Content-Type
should
+ * be replaced with a 502 error response.
+ */
+ @Test
+ void nonJsonBodyWithJsonContentTypeShouldReturn502() throws Exception {
+ MockHttpServletRequest request = createConsoleRequest("GET");
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json");
+ // Upstream sends HTML disguised as JSON
+ byte[] htmlBody =
"<html><script>alert('xss')</script></html>".getBytes(StandardCharsets.UTF_8);
+ ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+ htmlBody, responseHeaders, HttpStatus.OK);
+
+ when(restTemplate.exchange(any(URI.class), eq(HttpMethod.GET),
any(HttpEntity.class), eq(byte[].class)))
+ .thenReturn(upstreamResponse);
+
+ filter.doFilter(request, response, filterChain);
+
+ assertEquals(502, response.getStatus(),
+ "Should return 502 when upstream body is not valid JSON");
+ String body = response.getContentAsString();
+ assertEquals("{\"error\":\"Upstream returned invalid response
body\"}", body);
+ }
+
+ /**
+ * Helper: create a MockHttpServletRequest that matches the console URL
pattern
+ * and includes the required namespace/cluster headers.
+ */
+ private MockHttpServletRequest createConsoleRequest(String method) {
+ MockHttpServletRequest request = new MockHttpServletRequest(method,
"/api/v1/console/globalSession/query");
+ request.addHeader("x-seata-namespace", NAMESPACE);
+ request.addHeader("x-seata-cluster", CLUSTER);
+ return request;
+ }
+}
+
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]