This is an automated email from the ASF dual-hosted git repository.
jimin 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 78c275e23a refactor: run the HTTP filter as a chain of responsibility
(#7818)
78c275e23a is described below
commit 78c275e23ab1c7c53bbaa0209dd75a552f3688e5
Author: funkye <[email protected]>
AuthorDate: Sun Nov 30 19:03:36 2025 +0800
refactor: run the HTTP filter as a chain of responsibility (#7818)
---
.../rpc/netty/http/BaseHttpChannelHandler.java | 12 -
.../core/rpc/netty/http/Http2HttpHandler.java | 149 ++++++------
.../core/rpc/netty/http/HttpDispatchHandler.java | 149 ++++++------
.../core/rpc/netty/http/RequestParseUtils.java | 260 +++++++++++++++++++++
.../core/rpc/netty/http/SimpleHttp2Request.java | 47 ++++
.../rpc/netty/http/filter/HttpFilterContext.java | 35 ++-
.../rpc/netty/http/filter/HttpRequestFilter.java | 2 +-
.../netty/http/filter/HttpRequestFilterChain.java | 18 +-
.../http/filter/HttpRequestFilterManager.java | 10 +-
.../netty/http/filter/HttpRequestParamWrapper.java | 187 +++++----------
.../core/rpc/netty/http/Http2HttpHandlerTest.java | 181 +++++++-------
.../rpc/netty/http/HttpDispatchHandlerTest.java | 62 ++---
.../server/config/SeataNamingserverWebConfig.java | 39 ----
.../seata/server/filter/RaftRequestFilter.java | 110 ++++++---
.../seata/server/filter/XSSHttpRequestFilter.java | 9 +-
...ta.core.rpc.netty.http.filter.HttpRequestFilter | 3 +-
.../server/controller/ClusterControllerTest.java | 4 +-
17 files changed, 788 insertions(+), 489 deletions(-)
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
index 1dccda8e0c..995b4efb5f 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
@@ -19,11 +19,7 @@ package org.apache.seata.core.rpc.netty.http;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.seata.common.thread.NamedThreadFactory;
-import org.apache.seata.core.exception.HttpRequestFilterException;
import org.apache.seata.core.rpc.netty.NettyServerConfig;
-import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
-import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
-import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
@@ -48,12 +44,4 @@ public abstract class BaseHttpChannelHandler<T> extends
SimpleChannelInboundHand
static {
Runtime.getRuntime().addShutdownHook(new
Thread(HTTP_HANDLER_THREADS::shutdown));
}
-
- /**
- * The filter has a unified entry point and is called by subclasses at an
appropriate time
- */
- protected final void doFilterInternal(HttpFilterContext<?> context) throws
HttpRequestFilterException {
- HttpRequestFilterChain filterChain =
HttpRequestFilterManager.getFilterChain();
- filterChain.doFilter(context);
- }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
index 0ef8380500..6285180927 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
@@ -23,7 +23,6 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
@@ -33,15 +32,18 @@ import io.netty.handler.codec.http2.Http2HeadersFrame;
import io.netty.handler.codec.http2.Http2StreamFrame;
import org.apache.seata.common.rpc.http.HttpContext;
import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.RequestParseUtils.BodyParseResult;
+import org.apache.seata.core.rpc.netty.http.RequestParseUtils.QueryParseResult;
import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
import org.apache.seata.core.rpc.netty.http.filter.HttpRequestParamWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
-import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import static
org.apache.seata.core.protocol.ProtocolConstants.MAX_FRAME_LENGTH;
@@ -102,61 +104,77 @@ public class Http2HttpHandler extends
BaseHttpChannelHandler<Http2StreamFrame> {
HttpMethod method =
HttpMethod.valueOf(http2Headers.method().toString());
String path = http2Headers.path().toString();
String body = bodyBuffer != null ?
bodyBuffer.toString(StandardCharsets.UTF_8) : "";
- SimpleHttp2Request request = new SimpleHttp2Request(method, path,
http2Headers, body);
- // After receiving the complete request, the filtering logic is
executed
- HttpFilterContext<SimpleHttp2Request> context =
- new HttpFilterContext<>(request, () -> new
HttpRequestParamWrapper(request));
- doFilterInternal(context);
+ Map<String, List<String>> headerParams =
RequestParseUtils.copyHeaders(http2Headers);
+ QueryParseResult queryParseResult =
RequestParseUtils.parseQuery(path);
+ String requestPath = queryParseResult.getPath();
+ Map<String, List<String>> queryParams =
queryParseResult.getParameters();
+ BodyParseResult bodyParseResult =
RequestParseUtils.parseBody(OBJECT_MAPPER, body, http2Headers);
- // reuse HttpDispatchHandler logic
- boolean keepAlive = true; // In HTTP/2, connections are persistent
by default
- QueryStringDecoder queryStringDecoder = new
QueryStringDecoder(request.getPath());
- String requestPath = queryStringDecoder.path();
+ SimpleHttp2Request request = new SimpleHttp2Request(
+ method,
+ path,
+ http2Headers,
+ body,
+ queryParams,
+ bodyParseResult.getFormParams(),
+ headerParams,
+ bodyParseResult.getJsonParams(),
+ bodyParseResult.getBodyNode());
+
+ HttpFilterContext<SimpleHttp2Request> context = new
HttpFilterContext<>(
+ request,
+ ctx,
+ true,
+ HttpContext.HTTP_2_0,
+ () -> new HttpRequestParamWrapper(
+ queryParams,
+ bodyParseResult.getFormParams(),
+ headerParams,
+ bodyParseResult.getJsonParams()));
+
+ // Parse request
+ // queryStringDecoder already computed path/params above
HttpInvocation httpInvocation =
ControllerManager.getHttpInvocation(requestPath);
if (httpInvocation == null) {
sendErrorResponse(ctx, HttpResponseStatus.NOT_FOUND);
return;
}
- HttpContext<SimpleHttp2Request> httpContext =
- new HttpContext<>(request, ctx, keepAlive,
HttpContext.HTTP_2_0);
+
+ context.setAttribute("httpInvocation", httpInvocation);
+ context.setAttribute("httpController",
httpInvocation.getController());
+ context.setAttribute("handleMethod", httpInvocation.getMethod());
+
ObjectNode requestDataNode = OBJECT_MAPPER.createObjectNode();
- requestDataNode.set("param",
ParameterParser.convertParamMap(queryStringDecoder.parameters()));
- if (request.getMethod() == HttpMethod.POST
- && request.getBody() != null
- && !request.getBody().isEmpty()) {
- CharSequence contentTypeSeq =
request.getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
- String contentType = contentTypeSeq != null ?
contentTypeSeq.toString() : "";
+ requestDataNode.set("param",
ParameterParser.convertParamMap(queryParams));
+ if (request.getMethod() == HttpMethod.POST &&
request.getBodyNode() != null) {
+ requestDataNode.set("body", request.getBodyNode());
+ }
+ Method handleMethod = httpInvocation.getMethod();
+ Object[] args;
+ try {
+ args = ParameterParser.getArgValues(
+ httpInvocation.getParamMetaData(), handleMethod,
requestDataNode, context);
+ } catch (Exception e) {
+ LOGGER.error("Error parsing request arguments for HTTP/2: {}",
e.getMessage(), e);
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST);
+ return;
+ }
+ context.setAttribute("args", args);
+
+ // Execute filter chain in HTTP thread pool
+ HttpRequestFilterChain filterChain =
HttpRequestFilterManager.getFilterChain(this::executeFinalAction);
+ HTTP_HANDLER_THREADS.execute(() -> {
try {
- if (contentType.contains("application/json")) {
- ObjectNode bodyDataNode = (ObjectNode)
OBJECT_MAPPER.readTree(request.getBody());
- requestDataNode.set("body", bodyDataNode);
- } else if
(contentType.contains("application/x-www-form-urlencoded")) {
- Map<String, String> formParams = new HashMap<>();
- String[] pairs = request.getBody().split("&");
- for (String pair : pairs) {
- String[] kv = pair.split("=", 2);
- if (kv.length == 2) {
- String key = URLDecoder.decode(kv[0],
StandardCharsets.UTF_8.name());
- String value = URLDecoder.decode(kv[1],
StandardCharsets.UTF_8.name());
- formParams.put(key, value);
- }
- }
- ObjectNode formDataNode =
OBJECT_MAPPER.valueToTree(formParams);
- requestDataNode.set("body", formDataNode);
- }
+ filterChain.doFilter(context);
+ } catch (HttpRequestFilterException e) {
+ LOGGER.warn("Request blocked by filter while processing
HTTP2 request: {}", e.getMessage());
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST);
} catch (Exception e) {
- LOGGER.warn("Failed to parse http2 body: {}",
e.getMessage());
+ LOGGER.error("Exception occurred while processing HTTP2
request: {}", e.getMessage(), e);
+ sendErrorResponse(ctx,
HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
- }
- Object httpController = httpInvocation.getController();
- Method handleMethod = httpInvocation.getMethod();
- Object[] args = ParameterParser.getArgValues(
- httpInvocation.getParamMetaData(), handleMethod,
requestDataNode, httpContext);
- handle(httpController, handleMethod, args, ctx, httpContext);
- } catch (HttpRequestFilterException e) {
- LOGGER.warn("Request blocked by filter while processing HTTP2
request: {}", e.getMessage());
- sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST);
+ });
} catch (Exception e) {
LOGGER.error("Exception occurred while processing HTTP2 request:
{}", e.getMessage(), e);
sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
@@ -170,27 +188,24 @@ public class Http2HttpHandler extends
BaseHttpChannelHandler<Http2StreamFrame> {
}
}
- private void handle(
- Object httpController,
- Method handleMethod,
- Object[] args,
- ChannelHandlerContext ctx,
- HttpContext<SimpleHttp2Request> httpContext) {
- HTTP_HANDLER_THREADS.execute(() -> {
- Object result;
- try {
- result = handleMethod.invoke(httpController, args);
- if (!httpContext.isAsync()) {
- sendResponse(ctx, result);
- }
- } catch (IllegalAccessException e) {
- LOGGER.error("Illegal argument exception: {}", e.getMessage(),
e);
- sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST);
- } catch (Exception e) {
- LOGGER.error("Exception occurred while processing HTTP2
request: {}", e.getMessage(), e);
- sendErrorResponse(ctx,
HttpResponseStatus.INTERNAL_SERVER_ERROR);
+ private void executeFinalAction(HttpFilterContext<?> context) {
+ HttpInvocation httpInvocation = context.getAttribute("httpInvocation");
+ Object httpController = context.getAttribute("httpController");
+ Method handleMethod = context.getAttribute("handleMethod");
+ Object[] args = context.getAttribute("args");
+
+ try {
+ Object result = handleMethod.invoke(httpController, args);
+ if (!context.isAsync()) {
+ sendResponse(context.getContext(), result);
}
- });
+ } catch (IllegalAccessException e) {
+ LOGGER.error("Illegal argument exception: {}", e.getMessage(), e);
+ sendErrorResponse(context.getContext(),
HttpResponseStatus.BAD_REQUEST);
+ } catch (Exception e) {
+ LOGGER.error("Exception occurred while processing HTTP2 request:
{}", e.getMessage(), e);
+ sendErrorResponse(context.getContext(),
HttpResponseStatus.INTERNAL_SERVER_ERROR);
+ }
}
private void sendResponse(ChannelHandlerContext ctx, Object result) throws
Exception {
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
index caafc308fd..a8f9022f08 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
@@ -22,6 +22,7 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
@@ -29,19 +30,20 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http.QueryStringDecoder;
-import io.netty.handler.codec.http.multipart.Attribute;
-import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
-import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import org.apache.seata.common.rpc.http.HttpContext;
import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.RequestParseUtils.BodyParseResult;
+import org.apache.seata.core.rpc.netty.http.RequestParseUtils.QueryParseResult;
import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
import org.apache.seata.core.rpc.netty.http.filter.HttpRequestParamWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
-import java.util.concurrent.RejectedExecutionException;
+import java.util.List;
+import java.util.Map;
/**
* A Netty HTTP request handler that dispatches incoming requests to
corresponding controller methods
@@ -52,85 +54,86 @@ public class HttpDispatchHandler extends
BaseHttpChannelHandler<HttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest
httpRequest) {
- try {
- HttpFilterContext<HttpRequest> context =
- new HttpFilterContext<>(httpRequest, () -> new
HttpRequestParamWrapper(httpRequest));
- doFilterInternal(context);
- } catch (HttpRequestFilterException e) {
- LOGGER.warn("Request blocked by filter: {}", e.getMessage());
- sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, false);
- return;
- } catch (Exception e) {
- LOGGER.error("Unexpected error during filter execution: {}",
e.getMessage(), e);
- sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR,
false);
+ FullHttpRequest fullHttpRequest = httpRequest instanceof
FullHttpRequest ? (FullHttpRequest) httpRequest : null;
+ QueryParseResult queryParseResult =
RequestParseUtils.parseQuery(httpRequest.uri());
+ String path = queryParseResult.getPath();
+ Map<String, List<String>> queryParams =
queryParseResult.getParameters();
+ Map<String, List<String>> headerParams =
RequestParseUtils.copyHeaders(httpRequest.headers());
+ BodyParseResult bodyParseResult = fullHttpRequest != null
+ ? RequestParseUtils.parseBody(OBJECT_MAPPER, fullHttpRequest)
+ : RequestParseUtils.BodyParseResult.empty();
+
+ HttpFilterContext<HttpRequest> context = new HttpFilterContext<>(
+ httpRequest,
+ ctx,
+ HttpUtil.isKeepAlive(httpRequest)
+ && httpRequest.protocolVersion().isKeepAliveDefault(),
+ HttpContext.HTTP_1_1,
+ () -> new HttpRequestParamWrapper(
+ queryParams, bodyParseResult.getFormParams(),
headerParams, bodyParseResult.getJsonParams()));
+
+ HttpInvocation httpInvocation =
ControllerManager.getHttpInvocation(path);
+
+ if (httpInvocation == null) {
+ sendErrorResponse(ctx, HttpResponseStatus.NOT_FOUND, false);
return;
}
+ context.setAttribute("httpInvocation", httpInvocation);
+ context.setAttribute("httpController", httpInvocation.getController());
+ context.setAttribute("handleMethod", httpInvocation.getMethod());
+
+ ObjectNode requestDataNode = OBJECT_MAPPER.createObjectNode();
+ requestDataNode.set("param",
ParameterParser.convertParamMap(queryParams));
+ if (httpRequest.method() == HttpMethod.POST &&
bodyParseResult.getBodyNode() != null) {
+ requestDataNode.set("body", bodyParseResult.getBodyNode());
+ }
+
+ Object[] args;
try {
- boolean keepAlive = HttpUtil.isKeepAlive(httpRequest)
- && httpRequest.protocolVersion().isKeepAliveDefault();
- QueryStringDecoder queryStringDecoder = new
QueryStringDecoder(httpRequest.uri());
- String path = queryStringDecoder.path();
- HttpInvocation httpInvocation =
ControllerManager.getHttpInvocation(path);
-
- if (httpInvocation == null) {
- sendErrorResponse(ctx, HttpResponseStatus.NOT_FOUND, false);
- return;
- }
+ args = ParameterParser.getArgValues(
+ httpInvocation.getParamMetaData(),
httpInvocation.getMethod(), requestDataNode, context);
+ } catch (Exception e) {
+ LOGGER.error("Error parsing request arguments: {}",
e.getMessage(), e);
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, false);
+ return;
+ }
+ context.setAttribute("args", args);
- HttpContext<HttpRequest> httpContext = new
HttpContext<>(httpRequest, ctx, keepAlive, HttpContext.HTTP_1_1);
- ObjectNode requestDataNode = OBJECT_MAPPER.createObjectNode();
- requestDataNode.set("param",
ParameterParser.convertParamMap(queryStringDecoder.parameters()));
-
- if (httpRequest.method() == HttpMethod.POST) {
- HttpPostRequestDecoder httpPostRequestDecoder = null;
- try {
- httpPostRequestDecoder = new
HttpPostRequestDecoder(httpRequest);
- ObjectNode bodyDataNode = OBJECT_MAPPER.createObjectNode();
- for (InterfaceHttpData interfaceHttpData :
httpPostRequestDecoder.getBodyHttpDatas()) {
- if (interfaceHttpData.getHttpDataType() !=
InterfaceHttpData.HttpDataType.Attribute) {
- continue;
- }
- Attribute attribute = (Attribute) interfaceHttpData;
- bodyDataNode.put(attribute.getName(),
attribute.getValue());
- }
- requestDataNode.putIfAbsent("body", bodyDataNode);
- } finally {
- if (httpPostRequestDecoder != null) {
- httpPostRequestDecoder.destroy();
- }
- }
+ // Execute filter chain in HTTP thread pool
+ HttpRequestFilterChain filterChain =
HttpRequestFilterManager.getFilterChain(this::executeFinalAction);
+ HTTP_HANDLER_THREADS.execute(() -> {
+ try {
+ filterChain.doFilter(context);
+ } catch (HttpRequestFilterException e) {
+ LOGGER.warn("Request blocked by filter: {}", e.getMessage());
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, false);
+ } catch (Exception e) {
+ LOGGER.error("Unexpected error during request processing: {}",
e.getMessage(), e);
+ sendErrorResponse(ctx,
HttpResponseStatus.INTERNAL_SERVER_ERROR, false);
}
+ });
+ }
- Object httpController = httpInvocation.getController();
- Method handleMethod = httpInvocation.getMethod();
- Object[] args = ParameterParser.getArgValues(
- httpInvocation.getParamMetaData(), handleMethod,
requestDataNode, httpContext);
+ private void executeFinalAction(HttpFilterContext<?> context) {
+ HttpInvocation httpInvocation = context.getAttribute("httpInvocation");
+ Object httpController = context.getAttribute("httpController");
+ Method handleMethod = context.getAttribute("handleMethod");
+ Object[] args = context.getAttribute("args");
- try {
- HTTP_HANDLER_THREADS.execute(() -> {
- try {
- Object result = handleMethod.invoke(httpController,
args);
- if (httpContext.isAsync()) {
- return;
- }
-
- sendResponse(ctx, keepAlive, result);
- } catch (IllegalArgumentException e) {
- LOGGER.error("Illegal argument exception: {}",
e.getMessage(), e);
- sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST,
false);
- } catch (Exception e) {
- LOGGER.error("Exception occurred while processing HTTP
request: {}", e.getMessage(), e);
- sendErrorResponse(ctx,
HttpResponseStatus.INTERNAL_SERVER_ERROR, false);
- }
- });
- } catch (RejectedExecutionException e) {
- LOGGER.error("HTTP thread pool is full: {}", e.getMessage(),
e);
- sendErrorResponse(ctx, HttpResponseStatus.SERVICE_UNAVAILABLE,
false);
+ try {
+ Object result = handleMethod.invoke(httpController, args);
+ if (context.isAsync()) {
+ return;
}
+
+ sendResponse(context.getContext(), context.isKeepAlive(), result);
+ } catch (IllegalArgumentException e) {
+ LOGGER.error("Illegal argument exception: {}", e.getMessage(), e);
+ sendErrorResponse(context.getContext(),
HttpResponseStatus.BAD_REQUEST, false);
} catch (Exception e) {
LOGGER.error("Exception occurred while processing HTTP request:
{}", e.getMessage(), e);
- sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR,
false);
+ sendErrorResponse(context.getContext(),
HttpResponseStatus.INTERNAL_SERVER_ERROR, false);
}
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/RequestParseUtils.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/RequestParseUtils.java
new file mode 100644
index 0000000000..dd7516bb62
--- /dev/null
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/RequestParseUtils.java
@@ -0,0 +1,260 @@
+/*
+ * 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.core.rpc.netty.http;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import io.netty.handler.codec.http.multipart.Attribute;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http2.Http2Headers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Utility class that parses HTTP headers, query parameters and body payloads
so handlers can reuse the same results.
+ */
+public final class RequestParseUtils {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(RequestParseUtils.class);
+
+ private RequestParseUtils() {}
+
+ public static QueryParseResult parseQuery(String uri) {
+ QueryStringDecoder decoder = new QueryStringDecoder(uri);
+ return new QueryParseResult(decoder.path(),
deepCopyParameters(decoder.parameters()));
+ }
+
+ public static Map<String, List<String>> copyHeaders(HttpHeaders headers) {
+ Map<String, List<String>> headerParams = new HashMap<>();
+ for (Map.Entry<String, String> entry : headers) {
+ String key = entry.getKey();
+ headerParams.computeIfAbsent(key, k -> new
ArrayList<>()).add(entry.getValue());
+ String lowerKey = key != null ? key.toLowerCase(Locale.ROOT) :
null;
+ if (lowerKey != null && !lowerKey.equals(key)) {
+ headerParams.computeIfAbsent(lowerKey, k -> new
ArrayList<>()).add(entry.getValue());
+ }
+ }
+ return headerParams;
+ }
+
+ public static Map<String, List<String>> copyHeaders(Http2Headers headers) {
+ Map<String, List<String>> headerParams = new HashMap<>();
+ for (Map.Entry<CharSequence, CharSequence> entry : headers) {
+ String key = entry.getKey().toString();
+ String value = entry.getValue().toString();
+ headerParams.computeIfAbsent(key, k -> new
ArrayList<>()).add(value);
+ String lowerKey = key.toLowerCase(Locale.ROOT);
+ if (!lowerKey.equals(key)) {
+ headerParams.computeIfAbsent(lowerKey, k -> new
ArrayList<>()).add(value);
+ }
+ }
+ return headerParams;
+ }
+
+ public static Map<String, List<String>> deepCopyParameters(Map<String,
List<String>> source) {
+ Map<String, List<String>> target = new HashMap<>();
+ if (source == null) {
+ return target;
+ }
+ source.forEach((key, values) -> {
+ if (values != null) {
+ target.put(key, new ArrayList<>(values));
+ }
+ });
+ return target;
+ }
+
+ public static BodyParseResult parseBody(ObjectMapper objectMapper,
FullHttpRequest request) {
+ if (request == null || !request.content().isReadable()) {
+ return BodyParseResult.empty();
+ }
+ String contentType =
request.headers().get(HttpHeaderNames.CONTENT_TYPE);
+ if (contentType == null) {
+ return BodyParseResult.empty();
+ }
+ String lowerCaseContentType = contentType.toLowerCase(Locale.ROOT);
+ String bodyString = request.content().toString(StandardCharsets.UTF_8);
+
+ Map<String, List<String>> formParams = new HashMap<>();
+ Map<String, List<String>> jsonParams = new HashMap<>();
+ ObjectNode bodyNode = null;
+
+ try {
+ if (lowerCaseContentType.contains("application/json")) {
+ JsonNode node = objectMapper.readTree(bodyString);
+ if (node instanceof ObjectNode) {
+ bodyNode = (ObjectNode) node;
+ bodyNode.fields().forEachRemaining(entry -> jsonParams
+ .computeIfAbsent(entry.getKey(), k -> new
ArrayList<>())
+ .add(entry.getValue().asText()));
+ }
+ } else if
(lowerCaseContentType.contains("application/x-www-form-urlencoded")) {
+ bodyNode = objectMapper.createObjectNode();
+ decodeUrlEncoded(bodyString, formParams, bodyNode);
+ } else if (lowerCaseContentType.contains("multipart/form-data")) {
+ bodyNode = objectMapper.createObjectNode();
+ HttpPostRequestDecoder decoder = null;
+ try {
+ decoder = new HttpPostRequestDecoder(request);
+ for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
+ if (data.getHttpDataType() !=
InterfaceHttpData.HttpDataType.Attribute) {
+ continue;
+ }
+ Attribute attr = (Attribute) data;
+ try {
+ String name = attr.getName();
+ String value = attr.getValue();
+ formParams
+ .computeIfAbsent(name, k -> new
ArrayList<>())
+ .add(value);
+ bodyNode.put(name, value);
+ } finally {
+ attr.release();
+ }
+ }
+ } finally {
+ if (decoder != null) {
+ decoder.destroy();
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Failed to parse HTTP/1.x body: {}", e.getMessage());
+ }
+ return new BodyParseResult(formParams, jsonParams, bodyNode);
+ }
+
+ public static BodyParseResult parseBody(ObjectMapper objectMapper, String
body, Http2Headers headers) {
+ if (body == null || body.isEmpty()) {
+ return BodyParseResult.empty();
+ }
+ CharSequence contentTypeSeq =
headers.get(HttpHeaderNames.CONTENT_TYPE);
+ if (contentTypeSeq == null) {
+ return BodyParseResult.empty();
+ }
+ String contentType = contentTypeSeq.toString();
+ String lowerCaseContentType = contentType.toLowerCase(Locale.ROOT);
+
+ Map<String, List<String>> formParams = new HashMap<>();
+ Map<String, List<String>> jsonParams = new HashMap<>();
+ ObjectNode bodyNode = null;
+ try {
+ if (lowerCaseContentType.contains("application/json")) {
+ JsonNode node = objectMapper.readTree(body);
+ if (node instanceof ObjectNode) {
+ bodyNode = (ObjectNode) node;
+ bodyNode.fields().forEachRemaining(entry -> jsonParams
+ .computeIfAbsent(entry.getKey(), k -> new
ArrayList<>())
+ .add(entry.getValue().asText()));
+ }
+ } else if
(lowerCaseContentType.contains("application/x-www-form-urlencoded")) {
+ bodyNode = objectMapper.createObjectNode();
+ decodeUrlEncoded(body, formParams, bodyNode);
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Failed to parse HTTP/2 body: {}", e.getMessage());
+ }
+ return new BodyParseResult(formParams, jsonParams, bodyNode);
+ }
+
+ private static void decodeUrlEncoded(String body, Map<String,
List<String>> formParams, ObjectNode bodyNode) {
+ if (body == null || body.isEmpty()) {
+ return;
+ }
+ String[] pairs = body.split("&");
+ for (String pair : pairs) {
+ String[] kv = pair.split("=", 2);
+ if (kv.length != 2) {
+ continue;
+ }
+ String key = decodeComponent(kv[0]);
+ String value = decodeComponent(kv[1]);
+ formParams.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
+ bodyNode.put(key, value);
+ }
+ }
+
+ private static String decodeComponent(String value) {
+ try {
+ return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
+ } catch (Exception e) {
+ LOGGER.warn("Failed to decode form field: {}", value);
+ return value;
+ }
+ }
+
+ public static final class QueryParseResult {
+ private final String path;
+ private final Map<String, List<String>> parameters;
+
+ QueryParseResult(String path, Map<String, List<String>> parameters) {
+ this.path = path;
+ this.parameters = parameters;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Map<String, List<String>> getParameters() {
+ return parameters;
+ }
+ }
+
+ public static final class BodyParseResult {
+ private final Map<String, List<String>> formParams;
+ private final Map<String, List<String>> jsonParams;
+ private final ObjectNode bodyNode;
+
+ BodyParseResult(
+ Map<String, List<String>> formParams, Map<String,
List<String>> jsonParams, ObjectNode bodyNode) {
+ this.formParams = formParams;
+ this.jsonParams = jsonParams;
+ this.bodyNode = bodyNode;
+ }
+
+ public static BodyParseResult empty() {
+ return new BodyParseResult(new HashMap<>(), new HashMap<>(), null);
+ }
+
+ public Map<String, List<String>> getFormParams() {
+ return formParams;
+ }
+
+ public Map<String, List<String>> getJsonParams() {
+ return jsonParams;
+ }
+
+ public ObjectNode getBodyNode() {
+ return bodyNode;
+ }
+ }
+}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/SimpleHttp2Request.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/SimpleHttp2Request.java
index fe31fb6b7d..3c3c405ea4 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/SimpleHttp2Request.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/SimpleHttp2Request.java
@@ -16,20 +16,47 @@
*/
package org.apache.seata.core.rpc.netty.http;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http2.Http2Headers;
+import java.util.List;
+import java.util.Map;
+
public class SimpleHttp2Request {
private final HttpMethod method;
private final String path;
private final Http2Headers headers;
private final String body;
+ private final Map<String, List<String>> queryParams;
+ private final Map<String, List<String>> formParams;
+ private final Map<String, List<String>> headerParams;
+ private final Map<String, List<String>> jsonParams;
+ private final ObjectNode bodyNode;
public SimpleHttp2Request(HttpMethod method, String path, Http2Headers
headers, String body) {
+ this(method, path, headers, body, null, null, null, null, null);
+ }
+
+ public SimpleHttp2Request(
+ HttpMethod method,
+ String path,
+ Http2Headers headers,
+ String body,
+ Map<String, List<String>> queryParams,
+ Map<String, List<String>> formParams,
+ Map<String, List<String>> headerParams,
+ Map<String, List<String>> jsonParams,
+ ObjectNode bodyNode) {
this.method = method;
this.path = path;
this.headers = headers;
this.body = body;
+ this.queryParams = queryParams;
+ this.formParams = formParams;
+ this.headerParams = headerParams;
+ this.jsonParams = jsonParams;
+ this.bodyNode = bodyNode;
}
public HttpMethod getMethod() {
@@ -47,4 +74,24 @@ public class SimpleHttp2Request {
public String getBody() {
return body;
}
+
+ public Map<String, List<String>> getQueryParams() {
+ return queryParams;
+ }
+
+ public Map<String, List<String>> getFormParams() {
+ return formParams;
+ }
+
+ public Map<String, List<String>> getHeaderParams() {
+ return headerParams;
+ }
+
+ public Map<String, List<String>> getJsonParams() {
+ return jsonParams;
+ }
+
+ public ObjectNode getBodyNode() {
+ return bodyNode;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
index 0fac67cab6..e9eb2d9ab2 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
@@ -16,22 +16,28 @@
*/
package org.apache.seata.core.rpc.netty.http.filter;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.seata.common.rpc.http.HttpContext;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
-public class HttpFilterContext<T> {
- private final T request;
+public class HttpFilterContext<T> extends HttpContext<T> {
private final Supplier<HttpRequestParamWrapper> paramWrapperSupplier;
private volatile HttpRequestParamWrapper paramWrapper;
+ private final Map<String, Object> attributes = new ConcurrentHashMap<>();
- public HttpFilterContext(T request, Supplier<HttpRequestParamWrapper>
paramWrapperSupplier) {
- this.request = request;
+ public HttpFilterContext(
+ T request,
+ ChannelHandlerContext channelHandlerContext,
+ boolean keepAlive,
+ String httpVersion,
+ Supplier<HttpRequestParamWrapper> paramWrapperSupplier) {
+ super(request, channelHandlerContext, keepAlive, httpVersion);
this.paramWrapperSupplier = paramWrapperSupplier;
}
- public T getRequest() {
- return request;
- }
-
public HttpRequestParamWrapper getParamWrapper() {
if (paramWrapper == null) {
synchronized (this) {
@@ -42,4 +48,17 @@ public class HttpFilterContext<T> {
}
return paramWrapper;
}
+
+ public void setAttribute(String key, Object value) {
+ attributes.put(key, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <V> V getAttribute(String key) {
+ return (V) attributes.get(key);
+ }
+
+ public Object removeAttribute(String key) {
+ return attributes.remove(key);
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
index 96728f450e..703395ab9c 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
@@ -33,7 +33,7 @@ public interface HttpRequestFilter {
/**
* Executes the filter logic.
*/
- void doFilter(HttpFilterContext<?> context) throws
HttpRequestFilterException;
+ void doFilter(HttpFilterContext<?> context, HttpRequestFilterChain chain)
throws HttpRequestFilterException;
/**
* Determines if the filter should run.
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
index 7fd774e204..5bae906c4b 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
@@ -19,17 +19,29 @@ package org.apache.seata.core.rpc.netty.http.filter;
import org.apache.seata.core.exception.HttpRequestFilterException;
import java.util.List;
+import java.util.function.Consumer;
public class HttpRequestFilterChain {
private final List<HttpRequestFilter> filters;
+ private final Consumer<HttpFilterContext<?>> finalAction;
+ private int currentIndex = 0;
- public HttpRequestFilterChain(List<HttpRequestFilter> filters) {
+ public HttpRequestFilterChain(List<HttpRequestFilter> filters,
Consumer<HttpFilterContext<?>> finalAction) {
this.filters = filters;
+ this.finalAction = finalAction;
}
public void doFilter(HttpFilterContext<?> httpFilterContext) throws
HttpRequestFilterException {
- for (HttpRequestFilter filter : filters) {
- filter.doFilter(httpFilterContext);
+ if (currentIndex < filters.size()) {
+ HttpRequestFilter filter = filters.get(currentIndex++);
+ filter.doFilter(httpFilterContext, this);
+ } else {
+ // Execute final action
+ if (finalAction != null) {
+ finalAction.accept(httpFilterContext);
+ }
+ // Reset for next request
+ currentIndex = 0;
}
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
index 5099a60652..6e1949c7af 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
@@ -21,6 +21,7 @@ import org.apache.seata.common.loader.EnhancedServiceLoader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.function.Consumer;
public class HttpRequestFilterManager {
@@ -42,7 +43,7 @@ public class HttpRequestFilterManager {
}
HTTP_REQUEST_FILTERS.sort(Comparator.comparingInt(HttpRequestFilter::getOrder));
- HTTP_REQUEST_FILTER_CHAIN = new
HttpRequestFilterChain(HTTP_REQUEST_FILTERS);
+ HTTP_REQUEST_FILTER_CHAIN = new
HttpRequestFilterChain(HTTP_REQUEST_FILTERS, null);
initialized = true;
}
@@ -52,4 +53,11 @@ public class HttpRequestFilterManager {
}
return HTTP_REQUEST_FILTER_CHAIN;
}
+
+ public static HttpRequestFilterChain
getFilterChain(Consumer<HttpFilterContext<?>> finalAction) {
+ if (!initialized) {
+ throw new IllegalStateException("HttpRequestFilterManager not
initialized.");
+ }
+ return new HttpRequestFilterChain(HTTP_REQUEST_FILTERS, finalAction);
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
index 50af6437ff..afac73af9d 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
@@ -16,24 +16,17 @@
*/
package org.apache.seata.core.rpc.netty.http.filter;
-import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
-import io.netty.handler.codec.http.multipart.Attribute;
-import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
-import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http2.Http2Headers;
+import org.apache.seata.core.rpc.netty.http.RequestParseUtils;
import org.apache.seata.core.rpc.netty.http.SimpleHttp2Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -52,146 +45,73 @@ public class HttpRequestParamWrapper {
private final Map<String, List<String>> headerParams = new HashMap<>();
private final Map<String, List<String>> jsonParams = new HashMap<>();
+ public HttpRequestParamWrapper(
+ Map<String, List<String>> queryParams,
+ Map<String, List<String>> formParams,
+ Map<String, List<String>> headerParams,
+ Map<String, List<String>> jsonParams) {
+ mergeParams(this.queryParams, queryParams);
+ mergeParams(this.formParams, formParams);
+ mergeParams(this.headerParams, headerParams);
+ mergeParams(this.jsonParams, jsonParams);
+ }
+
public HttpRequestParamWrapper(HttpRequest httpRequest) {
if (!(httpRequest instanceof FullHttpRequest)) {
throw new IllegalArgumentException("HttpRequest must be
FullHttpRequest to read body.");
}
FullHttpRequest fullRequest = (FullHttpRequest) httpRequest;
- parseQueryParams(fullRequest);
- parseHeaders(fullRequest);
- parseBody(fullRequest);
+ parseQueryParams(fullRequest.uri());
+ parseHeaders(fullRequest.headers());
+ RequestParseUtils.BodyParseResult bodyParseResult =
RequestParseUtils.parseBody(OBJECT_MAPPER, fullRequest);
+ mergeParams(formParams, bodyParseResult.getFormParams());
+ mergeParams(jsonParams, bodyParseResult.getJsonParams());
}
public HttpRequestParamWrapper(SimpleHttp2Request request) {
- parseQueryParams(request.getPath());
- parseHeaders(request.getHeaders());
-
- CharSequence contentTypeSeq =
request.getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
- String contentType = contentTypeSeq != null ?
contentTypeSeq.toString() : null;
- if (contentType == null) {
- return;
+ if (request.getQueryParams() != null) {
+ mergeParams(queryParams, request.getQueryParams());
+ } else {
+ parseQueryParams(request.getPath());
}
- try {
- if (contentType.contains("application/json")) {
- parseJsonBody(request.getBody());
- } else if
(contentType.contains("application/x-www-form-urlencoded")) {
- parseFormUrlEncodedBody(request.getBody());
- }
- } catch (Exception e) {
- LOGGER.warn("Failed to parse HTTP/2 body: {}", e.getMessage(), e);
+ if (request.getHeaderParams() != null) {
+ mergeParams(headerParams, request.getHeaderParams());
+ } else if (request.getHeaders() != null) {
+ parseHeaders(request.getHeaders());
}
- }
-
- private void parseQueryParams(FullHttpRequest request) {
- QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
- queryParams.putAll(decoder.parameters());
- }
-
- private void parseQueryParams(String path) {
- QueryStringDecoder decoder = new QueryStringDecoder(path);
- queryParams.putAll(decoder.parameters());
- }
- private void parseHeaders(FullHttpRequest request) {
- for (Map.Entry<String, String> entry : request.headers()) {
- headerParams.computeIfAbsent(entry.getKey(), k -> new
ArrayList<>()).add(entry.getValue());
+ boolean hasBodyMaps = request.getFormParams() != null ||
request.getJsonParams() != null;
+ if (request.getFormParams() != null) {
+ mergeParams(formParams, request.getFormParams());
}
- }
-
- private void parseHeaders(Http2Headers headers) {
- for (Map.Entry<CharSequence, CharSequence> entry : headers) {
- headerParams
- .computeIfAbsent(entry.getKey().toString(), k -> new
ArrayList<>())
- .add(entry.getValue().toString());
+ if (request.getJsonParams() != null) {
+ mergeParams(jsonParams, request.getJsonParams());
}
- }
- private void parseFormUrlEncodedBody(String body) {
- if (body == null || body.trim().isEmpty()) {
- return;
- }
- String[] pairs = body.split("&");
- for (String pair : pairs) {
- String[] kv = pair.split("=", 2);
- if (kv.length == 2) {
- String key = decode(kv[0]);
- String value = decode(kv[1]);
- formParams.computeIfAbsent(key, k -> new
ArrayList<>()).add(value);
- }
+ if (!hasBodyMaps && request.getBody() != null) {
+ RequestParseUtils.BodyParseResult bodyParseResult =
+ RequestParseUtils.parseBody(OBJECT_MAPPER,
request.getBody(), request.getHeaders());
+ mergeParams(formParams, bodyParseResult.getFormParams());
+ mergeParams(jsonParams, bodyParseResult.getJsonParams());
}
}
- private String decode(String s) {
- try {
- return java.net.URLDecoder.decode(s,
StandardCharsets.UTF_8.name());
- } catch (Exception e) {
- LOGGER.warn("Failed to decode form field: {}", s, e);
- return s;
- }
- }
-
- private void parseBody(FullHttpRequest request) {
- String contentType =
request.headers().get(HttpHeaderNames.CONTENT_TYPE);
- if (contentType == null) {
- return;
- }
-
- ByteBuf originalContent = request.content();
- ByteBuf copiedBuf = Unpooled.copiedBuffer(originalContent);
-
- String bodyStr = copiedBuf.toString(StandardCharsets.UTF_8);
-
- try {
- if (contentType.contains("application/json")) {
- parseJsonBody(bodyStr);
- } else if
(contentType.contains("application/x-www-form-urlencoded")
- || contentType.contains("multipart/form-data")) {
- // Replace user-controlled URI with constant string during
internal FullHttpRequest construction for
- // decoding form parameters.
- FullHttpRequest copiedRequest = new DefaultFullHttpRequest(
- request.protocolVersion(), request.method(),
"/internal-safe-uri", copiedBuf);
- // Copy headers to ensure proper multipart parsing
- copiedRequest.headers().setAll(request.headers());
- parseFormBody(copiedRequest);
- }
- } catch (Exception e) {
- LOGGER.warn("Failed to parse HTTP body: {}", e.getMessage(), e);
- }
+ private void parseQueryParams(String uri) {
+ QueryStringDecoder decoder = new QueryStringDecoder(uri);
+ queryParams.putAll(decoder.parameters());
}
- private void parseJsonBody(String bodyStr) {
- try {
- JsonNode jsonNode = OBJECT_MAPPER.readTree(bodyStr);
- if (jsonNode != null && jsonNode.isObject()) {
- jsonNode.fields().forEachRemaining(e -> jsonParams
- .computeIfAbsent(e.getKey(), k -> new ArrayList<>())
- .add(e.getValue().asText()));
- }
- } catch (Exception e) {
- LOGGER.warn("Failed to parse JSON body: {}", e.getMessage(), e);
- }
+ private void parseHeaders(HttpHeaders headers) {
+ headers.forEach(entry -> headerParams
+ .computeIfAbsent(entry.getKey(), k -> new ArrayList<>())
+ .add(entry.getValue()));
}
- private void parseFormBody(FullHttpRequest request) {
- HttpPostRequestDecoder decoder = null;
- try {
- decoder = new HttpPostRequestDecoder(request);
- for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
- if (data.getHttpDataType() ==
InterfaceHttpData.HttpDataType.Attribute) {
- Attribute attr = (Attribute) data;
- formParams
- .computeIfAbsent(attr.getName(), k -> new
ArrayList<>())
- .add(attr.getValue());
- }
- }
- } catch (Exception e) {
- LOGGER.warn("Failed to parse form body: {}", e.getMessage(), e);
- } finally {
- if (decoder != null) {
- decoder.destroy();
- }
- }
+ private void parseHeaders(Http2Headers headers) {
+ headers.forEach(entry -> headerParams
+ .computeIfAbsent(entry.getKey().toString(), k -> new
ArrayList<>())
+ .add(entry.getValue().toString()));
}
/**
@@ -211,4 +131,17 @@ public class HttpRequestParamWrapper {
return all;
}
+
+ private void mergeParams(Map<String, List<String>> target, Map<String,
List<String>> source) {
+ if (source == null || source.isEmpty()) {
+ return;
+ }
+ source.forEach((key, values) -> {
+ if (values == null || values.isEmpty()) {
+ return;
+ }
+ List<String> targetList = target.computeIfAbsent(key, k -> new
ArrayList<>());
+ targetList.addAll(values);
+ });
+ }
}
diff --git
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
index b6bdfff899..b68411d52a 100644
---
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
+++
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
@@ -95,29 +95,26 @@ class Http2HttpHandlerTest {
@Test
void testHttp2GetRequestWithParameters() throws Exception {
- try (MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class)) {
- HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
- doNothing().when(mockChain).doFilter(any());
-
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
- Http2Headers headers = new DefaultHttp2Headers();
- headers.method("GET");
- headers.path("/test?param=testValue");
- Http2HeadersFrame headersFrame = new
DefaultHttp2HeadersFrame(headers, true);
- channel.writeInbound(headersFrame);
+ HttpRequestFilterManager.initializeFilters();
- Http2StreamFrame responseHeadersFrame = waitForHttp2Response(5000);
- assertNotNull(responseHeadersFrame);
- assertTrue(responseHeadersFrame instanceof
DefaultHttp2HeadersFrame);
- DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame)
responseHeadersFrame;
- assertEquals("200", respHeaders.headers().status().toString());
-
- Http2StreamFrame responseDataFrame = waitForHttp2Response(5000);
- assertNotNull(responseDataFrame);
- assertTrue(responseDataFrame instanceof DefaultHttp2DataFrame);
- DefaultHttp2DataFrame respData = (DefaultHttp2DataFrame)
responseDataFrame;
- String content =
respData.content().toString(StandardCharsets.UTF_8);
- assertTrue(content.contains("Processed: testValue"));
- }
+ Http2Headers headers = new DefaultHttp2Headers();
+ headers.method("GET");
+ headers.path("/test?param=testValue");
+ Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers,
true);
+ channel.writeInbound(headersFrame);
+
+ Http2StreamFrame responseHeadersFrame = waitForHttp2Response(5000);
+ assertNotNull(responseHeadersFrame);
+ assertTrue(responseHeadersFrame instanceof DefaultHttp2HeadersFrame);
+ DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame)
responseHeadersFrame;
+ assertEquals("200", respHeaders.headers().status().toString());
+
+ Http2StreamFrame responseDataFrame = waitForHttp2Response(5000);
+ assertNotNull(responseDataFrame);
+ assertTrue(responseDataFrame instanceof DefaultHttp2DataFrame);
+ DefaultHttp2DataFrame respData = (DefaultHttp2DataFrame)
responseDataFrame;
+ String content = respData.content().toString(StandardCharsets.UTF_8);
+ assertTrue(content.contains("Processed: testValue"));
}
@Test
@@ -141,46 +138,43 @@ class Http2HttpHandlerTest {
@Test
void testHttp2PostRequestWithJsonBody() throws Exception {
- try (MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class)) {
- HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
- doNothing().when(mockChain).doFilter(any());
-
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
- String json = OBJECT_MAPPER.writeValueAsString(new HashMap<String,
Object>() {
- {
- put("foo", "bar");
- }
- });
- Http2Headers headers = new DefaultHttp2Headers();
- headers.method("POST");
- headers.path("/test?param=jsonValue");
- Http2HeadersFrame headersFrame = new
DefaultHttp2HeadersFrame(headers, false);
- channel.writeInbound(headersFrame);
- DefaultHttp2DataFrame dataFrame =
- new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json,
StandardCharsets.UTF_8), true);
- channel.writeInbound(dataFrame);
+ HttpRequestFilterManager.initializeFilters();
- Http2StreamFrame frame1 = null, frame2 = null;
- long deadline = System.currentTimeMillis() + 5000;
- while ((frame1 == null || frame2 == null) &&
System.currentTimeMillis() < deadline) {
- if (frame1 == null) frame1 = channel.readOutbound();
- if (frame2 == null) frame2 = channel.readOutbound();
- if (frame1 == null || frame2 == null) Thread.sleep(500);
+ String json = OBJECT_MAPPER.writeValueAsString(new HashMap<String,
Object>() {
+ {
+ put("foo", "bar");
}
- assertNotNull(frame1);
- assertNotNull(frame2);
- DefaultHttp2HeadersFrame respHeaders;
- DefaultHttp2DataFrame respData;
- if (frame1 instanceof DefaultHttp2HeadersFrame) {
- respHeaders = (DefaultHttp2HeadersFrame) frame1;
- respData = (DefaultHttp2DataFrame) frame2;
- } else {
- respHeaders = (DefaultHttp2HeadersFrame) frame2;
- respData = (DefaultHttp2DataFrame) frame1;
- }
- assertEquals("200", respHeaders.headers().status().toString());
- String content =
respData.content().toString(StandardCharsets.UTF_8);
- assertTrue(content.contains("Processed: jsonValue"));
+ });
+ Http2Headers headers = new DefaultHttp2Headers();
+ headers.method("POST");
+ headers.path("/test?param=jsonValue");
+ Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers,
false);
+ channel.writeInbound(headersFrame);
+ DefaultHttp2DataFrame dataFrame =
+ new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json,
StandardCharsets.UTF_8), true);
+ channel.writeInbound(dataFrame);
+
+ Http2StreamFrame frame1 = null, frame2 = null;
+ long deadline = System.currentTimeMillis() + 5000;
+ while ((frame1 == null || frame2 == null) &&
System.currentTimeMillis() < deadline) {
+ if (frame1 == null) frame1 = channel.readOutbound();
+ if (frame2 == null) frame2 = channel.readOutbound();
+ if (frame1 == null || frame2 == null) Thread.sleep(500);
+ }
+ assertNotNull(frame1);
+ assertNotNull(frame2);
+ DefaultHttp2HeadersFrame respHeaders;
+ DefaultHttp2DataFrame respData;
+ if (frame1 instanceof DefaultHttp2HeadersFrame) {
+ respHeaders = (DefaultHttp2HeadersFrame) frame1;
+ respData = (DefaultHttp2DataFrame) frame2;
+ } else {
+ respHeaders = (DefaultHttp2HeadersFrame) frame2;
+ respData = (DefaultHttp2DataFrame) frame1;
}
+ assertEquals("200", respHeaders.headers().status().toString());
+ String content = respData.content().toString(StandardCharsets.UTF_8);
+ assertTrue(content.contains("Processed: jsonValue"));
}
@Test
@@ -212,8 +206,10 @@ class Http2HttpHandlerTest {
.when(mockChain)
.doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
channel.writeInbound(headersFrame);
-
Http2StreamFrame responseHeadersFrame = waitForHttp2Response(3000);
assertNotNull(responseHeadersFrame);
assertTrue(responseHeadersFrame instanceof
DefaultHttp2HeadersFrame);
@@ -231,6 +227,9 @@ class Http2HttpHandlerTest {
.when(mockChain)
.doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
Http2Headers headers = new DefaultHttp2Headers();
headers.method("GET");
headers.path("/test?param=onload=alert(1)");
@@ -255,6 +254,9 @@ class Http2HttpHandlerTest {
.when(mockChain)
.doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
String maliciousJson = "{\"param\":
\"<script>alert('xss')</script>\"}";
Http2Headers headers = new DefaultHttp2Headers();
@@ -280,38 +282,34 @@ class Http2HttpHandlerTest {
@Test
void testHttp2PostRequestWithMultipleDataFrames() throws Exception {
- try (MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class)) {
- HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
- doNothing().when(mockChain).doFilter(any());
-
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
-
- Http2Headers headers = new DefaultHttp2Headers();
- headers.method("POST");
- headers.path("/test?param=multiFrame");
-
- Http2HeadersFrame headersFrame = new
DefaultHttp2HeadersFrame(headers, false);
- channel.writeInbound(headersFrame);
+ HttpRequestFilterManager.initializeFilters();
- String json1 = "{\"foo\":";
- DefaultHttp2DataFrame dataFrame1 =
- new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json1,
StandardCharsets.UTF_8), false);
- channel.writeInbound(dataFrame1);
-
- String json2 = "\"bar\"}";
- DefaultHttp2DataFrame dataFrame2 =
- new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json2,
StandardCharsets.UTF_8), true);
- channel.writeInbound(dataFrame2);
-
- Http2StreamFrame frame1 = null, frame2 = null;
- long deadline = System.currentTimeMillis() + 5000;
- while ((frame1 == null || frame2 == null) &&
System.currentTimeMillis() < deadline) {
- if (frame1 == null) frame1 = channel.readOutbound();
- if (frame2 == null) frame2 = channel.readOutbound();
- if (frame1 == null || frame2 == null) Thread.sleep(500);
- }
- assertNotNull(frame1);
- assertNotNull(frame2);
+ Http2Headers headers = new DefaultHttp2Headers();
+ headers.method("POST");
+ headers.path("/test?param=multiFrame");
+
+ Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers,
false);
+ channel.writeInbound(headersFrame);
+
+ String json1 = "{\"foo\":";
+ DefaultHttp2DataFrame dataFrame1 =
+ new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json1,
StandardCharsets.UTF_8), false);
+ channel.writeInbound(dataFrame1);
+
+ String json2 = "\"bar\"}";
+ DefaultHttp2DataFrame dataFrame2 =
+ new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json2,
StandardCharsets.UTF_8), true);
+ channel.writeInbound(dataFrame2);
+
+ Http2StreamFrame frame1 = null, frame2 = null;
+ long deadline = System.currentTimeMillis() + 5000;
+ while ((frame1 == null || frame2 == null) &&
System.currentTimeMillis() < deadline) {
+ if (frame1 == null) frame1 = channel.readOutbound();
+ if (frame2 == null) frame2 = channel.readOutbound();
+ if (frame1 == null || frame2 == null) Thread.sleep(500);
}
+ assertNotNull(frame1);
+ assertNotNull(frame2);
}
@Test
@@ -373,5 +371,8 @@ class Http2HttpHandlerTest {
field.setAccessible(true);
Map<String, HttpInvocation> map = (Map<String, HttpInvocation>)
field.get(null);
map.clear();
+ Field field2 =
HttpRequestFilterManager.class.getDeclaredField("initialized");
+ field2.setAccessible(true);
+ field2.set(null, false);
}
}
diff --git
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
index 8f41cc06b7..de5ef2f198 100644
---
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
+++
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
@@ -26,6 +26,7 @@ import io.netty.handler.codec.http.HttpVersion;
import org.apache.seata.core.exception.HttpRequestFilterException;
import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
@@ -56,13 +57,9 @@ class HttpDispatchHandlerTest {
}
@BeforeEach
- void setUp() {
+ void setUp() throws NoSuchMethodException {
handler = new HttpDispatchHandler();
channel = new EmbeddedChannel(handler);
- }
-
- @Test
- void testGetRequestWithParameters() throws Exception {
Method method = TestController.class.getMethod("handleRequest",
String.class);
ParamMetaData paramMetaData = new ParamMetaData();
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
@@ -76,11 +73,22 @@ class HttpDispatchHandlerTest {
invocation.setParamMetaData(paramMetaDatas);
ControllerManager.addHttpInvocation(invocation);
+ }
+
+ @AfterEach
+ void after() throws Exception {
+ clearControllerManager();
+ Field field2 =
HttpRequestFilterManager.class.getDeclaredField("initialized");
+ field2.setAccessible(true);
+ field2.set(null, false);
+ }
+
+ @Test
+ void testGetRequestWithParameters() throws Exception {
+
+ HttpRequestFilterManager.initializeFilters();
+ try {
- try (MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class)) {
- HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
- doNothing().when(mockChain).doFilter(any());
-
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
HttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, "/test?param=testValue");
@@ -136,9 +144,9 @@ class HttpDispatchHandlerTest {
MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class);
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockFilterChain);
-
+ mockedStatic.when(() ->
HttpRequestFilterManager.getFilterChain(any())).thenReturn(mockFilterChain);
try {
- HttpRequest request = new
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/any");
+ HttpRequest request = new
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test");
channel.writeInbound(request);
@@ -157,7 +165,9 @@ class HttpDispatchHandlerTest {
.when(mockChain)
.doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
-
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
HttpRequest request = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET,
"/test?param=<script>alert(1)</script>");
@@ -175,7 +185,9 @@ class HttpDispatchHandlerTest {
.when(mockChain)
.doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
-
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
HttpRequest request = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET,
"/test?param=javascript:alert('XSS')");
@@ -193,7 +205,9 @@ class HttpDispatchHandlerTest {
.when(mockChain)
.doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
-
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
HttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, "/test?param=onload=alert(1)");
@@ -218,11 +232,8 @@ class HttpDispatchHandlerTest {
invocation.setParamMetaData(paramMetaDatas);
ControllerManager.addHttpInvocation(invocation);
-
- try (MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class)) {
- HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
- doNothing().when(mockChain).doFilter(any());
-
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+ HttpRequestFilterManager.initializeFilters();
+ try {
String body = "param=postValue";
DefaultFullHttpRequest request = new DefaultFullHttpRequest(
@@ -259,11 +270,8 @@ class HttpDispatchHandlerTest {
invocation.setParamMetaData(paramMetaDatas);
ControllerManager.addHttpInvocation(invocation);
-
- try (MockedStatic<HttpRequestFilterManager> mockedStatic =
mockStatic(HttpRequestFilterManager.class)) {
- HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
- doNothing().when(mockChain).doFilter(any());
-
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+ HttpRequestFilterManager.initializeFilters();
+ try {
HttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET, "/testClose?param=closeValue");
@@ -284,8 +292,10 @@ class HttpDispatchHandlerTest {
HttpRequestFilterChain mockChain =
mock(HttpRequestFilterChain.class);
doThrow(new RuntimeException("Unexpected
error")).when(mockChain).doFilter(any());
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
-
- HttpRequest request = new
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/any");
+ mockedStatic
+ .when(() -> HttpRequestFilterManager.getFilterChain(any()))
+ .thenReturn(mockChain);
+ HttpRequest request = new
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test");
channel.writeInbound(request);
diff --git
a/server/src/main/java/org/apache/seata/server/config/SeataNamingserverWebConfig.java
b/server/src/main/java/org/apache/seata/server/config/SeataNamingserverWebConfig.java
deleted file mode 100644
index ca85b8359d..0000000000
---
a/server/src/main/java/org/apache/seata/server/config/SeataNamingserverWebConfig.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.server.config;
-
-import org.apache.seata.server.filter.RaftCondition;
-import org.apache.seata.server.filter.RaftRequestFilter;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Conditional;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.DependsOn;
-
-@Configuration
-public class SeataNamingserverWebConfig {
-
- @Bean
- @Conditional(RaftCondition.class)
- @DependsOn("raftRequestFilter")
- public FilterRegistrationBean<RaftRequestFilter>
raftRequestServletFilter(RaftRequestFilter raftRequestFilter) {
- FilterRegistrationBean<RaftRequestFilter> registrationBean = new
FilterRegistrationBean<>();
- registrationBean.setFilter(raftRequestFilter);
- registrationBean.addUrlPatterns("/api/v1/console/*", "/vgroup/v1/*");
- return registrationBean;
- }
-}
diff --git
a/server/src/main/java/org/apache/seata/server/filter/RaftRequestFilter.java
b/server/src/main/java/org/apache/seata/server/filter/RaftRequestFilter.java
index 10d5ae6c2f..24d24544a5 100644
--- a/server/src/main/java/org/apache/seata/server/filter/RaftRequestFilter.java
+++ b/server/src/main/java/org/apache/seata/server/filter/RaftRequestFilter.java
@@ -16,27 +16,23 @@
*/
package org.apache.seata.server.filter;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http2.Http2Headers;
+import org.apache.seata.common.loader.LoadLevel;
import org.apache.seata.common.store.SessionMode;
import org.apache.seata.common.util.StringUtils;
-import org.apache.seata.core.exception.TransactionException;
-import org.apache.seata.core.exception.TransactionExceptionCode;
+import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.SimpleHttp2Request;
+import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
import org.apache.seata.server.cluster.listener.ClusterChangeEvent;
import org.apache.seata.server.cluster.raft.context.SeataClusterContext;
-import org.apache.seata.server.console.exception.ConsoleException;
import org.apache.seata.server.store.StoreConfig;
import org.springframework.context.ApplicationListener;
-import org.springframework.context.annotation.Conditional;
-import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@@ -44,52 +40,75 @@ import java.util.concurrent.ConcurrentHashMap;
import static org.apache.seata.common.Constants.RAFT_GROUP_HEADER;
/**
- * Raft Leader Write Filter
+ * Raft Leader Write Filter for HTTP requests
*/
+@LoadLevel(name = "RaftRequest", order = 1)
@Component
-@Conditional(RaftCondition.class)
-public class RaftRequestFilter implements Filter,
ApplicationListener<ClusterChangeEvent> {
+public class RaftRequestFilter implements HttpRequestFilter,
ApplicationListener<ClusterChangeEvent> {
private static final Map<String, Boolean> GROUP_PREVENT = new
ConcurrentHashMap<>();
@Override
- public void init(FilterConfig filterConfig) throws ServletException {}
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain)
- throws IOException, ServletException {
- HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
- String group = httpRequest.getParameter("unit");
- if (StringUtils.isBlank(group)) {
- group = httpRequest.getHeader(RAFT_GROUP_HEADER);
+ public void doFilter(HttpFilterContext<?> context, HttpRequestFilterChain
chain) throws HttpRequestFilterException {
+ String uri = getUri(context);
+ if (!isTargetUri(uri)) {
+ chain.doFilter(context);
+ return;
}
+ String group = getGroup(context);
if (group != null) {
SeataClusterContext.bindGroup(group);
}
try {
- String method = httpRequest.getMethod();
- if (!HttpMethod.GET.name().equalsIgnoreCase(method)) {
+ String method;
+ if (context.getRequest() instanceof SimpleHttp2Request) {
+ SimpleHttp2Request request = (SimpleHttp2Request)
context.getRequest();
+ method = request.getMethod().name();
+ } else {
+ HttpRequest request = (HttpRequest) context.getRequest();
+ method = request.method().name();
+ }
+ if (!"GET".equalsIgnoreCase(method)) {
if (!isPass(group)) {
- throw new ConsoleException(
- new TransactionException(
- TransactionExceptionCode.NotRaftLeader,
- " The current TC is not a leader node,
interrupt processing of transactions!"),
- " The current TC is not a leader node, interrupt
processing of transactions!");
+ throw new HttpRequestFilterException(
+ "The current TC is not a leader node, interrupt
processing of transactions!");
}
}
- filterChain.doFilter(servletRequest, servletResponse);
+ chain.doFilter(context);
} finally {
SeataClusterContext.unbindGroup();
}
}
+ private String getGroup(HttpFilterContext<?> context) {
+ // Try to get from query params
+ Map<String, List<String>> params =
context.getParamWrapper().getAllParamsAsMultiMap();
+ List<String> unitParams = params.get("unit");
+ if (unitParams != null && !unitParams.isEmpty()) {
+ return unitParams.get(0);
+ }
+ // Try to get the group header from HttpRequest or SimpleHttp2Request
+ if (context.getRequest() instanceof
io.netty.handler.codec.http.HttpRequest) {
+ io.netty.handler.codec.http.HttpRequest httpRequest =
+ (io.netty.handler.codec.http.HttpRequest)
context.getRequest();
+ return httpRequest.headers().get(RAFT_GROUP_HEADER);
+ } else if (context.getRequest() instanceof SimpleHttp2Request) {
+ Http2Headers http2Headers = ((SimpleHttp2Request)
context.getRequest()).getHeaders();
+ CharSequence headerValue = http2Headers.get(RAFT_GROUP_HEADER);
+ return headerValue != null ? headerValue.toString() : null;
+ }
+ return null;
+ }
+
@Override
- public void onApplicationEvent(ClusterChangeEvent event) {
- setPrevent(event.getGroup(), event.isLeader());
+ public boolean shouldApply() {
+ return StoreConfig.getSessionMode() == SessionMode.RAFT;
}
@Override
- public void destroy() {}
+ public void onApplicationEvent(ClusterChangeEvent event) {
+ setPrevent(event.getGroup(), event.isLeader());
+ }
public static void setPrevent(String group, boolean prevent) {
if (StoreConfig.getSessionMode() == SessionMode.RAFT) {
@@ -101,4 +120,23 @@ public class RaftRequestFilter implements Filter,
ApplicationListener<ClusterCha
// Non-raft mode always allows requests
return Optional.ofNullable(GROUP_PREVENT.get(group)).orElse(false);
}
+
+ private String getUri(HttpFilterContext<?> context) {
+ // Extract URI from HttpRequest or path from SimpleHttp2Request
+ if (context.getRequest() instanceof
io.netty.handler.codec.http.HttpRequest) {
+ io.netty.handler.codec.http.HttpRequest httpRequest =
+ (io.netty.handler.codec.http.HttpRequest)
context.getRequest();
+ return httpRequest.uri();
+ } else if (context.getRequest() instanceof SimpleHttp2Request) {
+ return ((SimpleHttp2Request) context.getRequest()).getPath();
+ }
+ return null;
+ }
+
+ private boolean isTargetUri(String uri) {
+ if (StringUtils.isBlank(uri)) {
+ return false;
+ }
+ return uri.startsWith("/api/v1/console/") ||
uri.startsWith("/vgroup/v1/");
+ }
}
diff --git
a/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java
b/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java
index baaee393af..9839fdcc7f 100644
---
a/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java
+++
b/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java
@@ -27,6 +27,7 @@ import org.apache.seata.config.ConfigurationFactory;
import org.apache.seata.core.exception.HttpRequestFilterException;
import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
import java.util.ArrayList;
import java.util.Collections;
@@ -43,7 +44,7 @@ import static
org.apache.seata.common.DefaultValues.DEFAULT_XSS_KEYWORDS;
/**
* Filter to detect and block potential XSS attack vectors in HTTP request
parameters.
*/
-@LoadLevel(name = "XSS", order = 1)
+@LoadLevel(name = "XSS", order = Integer.MIN_VALUE)
public class XSSHttpRequestFilter implements HttpRequestFilter {
/**
* The constant CONFIG.
@@ -95,14 +96,14 @@ public class XSSHttpRequestFilter implements
HttpRequestFilter {
@Override
public int getOrder() {
- return 1;
+ return Integer.MIN_VALUE;
}
/**
* Checks all request parameters for XSS risks and throws if found.
*/
@Override
- public void doFilter(HttpFilterContext<?> context) throws
HttpRequestFilterException {
+ public void doFilter(HttpFilterContext<?> context, HttpRequestFilterChain
chain) throws HttpRequestFilterException {
Map<String, List<String>> allParams =
context.getParamWrapper().getAllParamsAsMultiMap();
for (Map.Entry<String, List<String>> entry : allParams.entrySet()) {
for (String value : entry.getValue()) {
@@ -112,6 +113,8 @@ public class XSSHttpRequestFilter implements
HttpRequestFilter {
}
}
}
+ // Continue to next filter
+ chain.doFilter(context);
}
/**
diff --git
a/server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
b/server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
index 09ff4f7e00..63ad553ffb 100644
---
a/server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
+++
b/server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
@@ -14,4 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-org.apache.seata.server.filter.XSSHttpRequestFilter
\ No newline at end of file
+org.apache.seata.server.filter.XSSHttpRequestFilter
+org.apache.seata.server.filter.RaftRequestFilter
\ No newline at end of file
diff --git
a/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
b/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
index 478358783e..710b61e56b 100644
---
a/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
+++
b/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
@@ -239,7 +239,7 @@ class ClusterControllerTest extends BaseSpringBootTest {
}
};
- HttpClientUtil.doPostWithHttp2("http://127.0.0.1:" + port + "/random",
params, header, callback, 5000);
+ HttpClientUtil.doPostWithHttp2("http://127.0.0.1:" + port + "/health",
params, header, callback, 5000);
assertTrue(latch.await(10, TimeUnit.SECONDS));
}
@@ -274,7 +274,7 @@ class ClusterControllerTest extends BaseSpringBootTest {
}
};
- HttpClientUtil.doPostWithHttp2("http://127.0.0.1:" + port + "/random",
jsonBody, header, callback, 5000);
+ HttpClientUtil.doPostWithHttp2("http://127.0.0.1:" + port + "/health",
jsonBody, header, callback, 5000);
assertTrue(latch.await(10, TimeUnit.SECONDS));
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]