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 bf6601f5fb feature: support http2 (#7451)
bf6601f5fb is described below
commit bf6601f5fb53d7b37d2fa7598a1bc0c3bae0b221
Author: funkye <[email protected]>
AuthorDate: Wed Jul 2 15:47:35 2025 +0800
feature: support http2 (#7451)
---
changes/en-us/2.x.md | 1 +
changes/zh-cn/2.x.md | 2 +-
.../apache/seata/common/rpc/http/HttpContext.java | 50 ++++--
.../core/protocol/detector/Http2Detector.java | 30 +++-
.../seata/core/protocol/detector/HttpDetector.java | 69 ++++++-
.../rpc/netty/http/BaseHttpChannelHandler.java | 47 +++++
.../core/rpc/netty/http/Http2HttpHandler.java | 199 +++++++++++++++++++++
.../core/rpc/netty/http/HttpDispatchHandler.java | 33 +---
.../core/rpc/netty/http/SimpleHttp2Request.java | 50 ++++++
.../core/protocol/detector/Http2DetectorTest.java | 65 +++++++
.../core/protocol/detector/HttpDetectorTest.java | 93 +++++++++-
.../core/rpc/netty/http/Http2HttpHandlerTest.java | 179 ++++++++++++++++++
.../cluster/manager/ClusterWatcherManager.java | 36 ++--
13 files changed, 785 insertions(+), 69 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 6b0e7e7f57..5a4418005c 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -21,6 +21,7 @@ Add changes here for all PR submitted to the 2.x branch.
### feature:
- [[#7261](https://github.com/apache/incubator-seata/pull/7261)] enforce
account initialization and disable default credentials
+- [[#7451](https://github.com/apache/incubator-seata/pull/7451)] seata-server
supports the HTTP/2 protocol
### bugfix:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 145279ed99..0381b40c3d 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -21,7 +21,7 @@
### feature:
- [[#7261](https://github.com/apache/incubator-seata/pull/7261)]
强制进行账户初始化并禁用默认凭据
-
+- [[#7451](https://github.com/apache/incubator-seata/pull/7451)]
seata-server支持HTTP/2协议
### bugfix:
diff --git
a/common/src/main/java/org/apache/seata/common/rpc/http/HttpContext.java
b/common/src/main/java/org/apache/seata/common/rpc/http/HttpContext.java
index d3d60fc639..7b984d68ee 100644
--- a/common/src/main/java/org/apache/seata/common/rpc/http/HttpContext.java
+++ b/common/src/main/java/org/apache/seata/common/rpc/http/HttpContext.java
@@ -17,37 +17,45 @@
package org.apache.seata.common.rpc.http;
import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.HttpRequest;
-public class HttpContext {
+public class HttpContext<T> {
- HttpRequest request;
+ public static final String HTTP_1_1 = "HTTP/1.1";
+ public static final String HTTP_2_0 = "HTTP/2.0";
- ChannelHandlerContext context;
+ private T request;
- boolean keepAlive;
+ private ChannelHandlerContext context;
- boolean async = false;
+ private boolean keepAlive;
- public HttpContext(HttpRequest request, ChannelHandlerContext context,
boolean keepAlive) {
+ private boolean async = false;
+
+ private String httpVersion;
+
+ public HttpContext(T request, ChannelHandlerContext context, boolean
keepAlive, String httpVersion) {
this.request = request;
this.context = context;
this.keepAlive = keepAlive;
+ this.httpVersion = httpVersion;
}
- public boolean isAsync() {
- return async;
+ public HttpContext(T request, ChannelHandlerContext context, boolean
keepAlive) {
+ this.request = request;
+ this.context = context;
+ this.keepAlive = keepAlive;
+ this.httpVersion = HTTP_1_1;
}
- public void setAsync(boolean async) {
- this.async = async;
+ public boolean isHttp2() {
+ return HTTP_2_0.equals(httpVersion);
}
- public HttpRequest getRequest() {
+ public T getRequest() {
return request;
}
- public void setRequest(HttpRequest request) {
+ public void setRequest(T request) {
this.request = request;
}
@@ -66,4 +74,20 @@ public class HttpContext {
public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}
+
+ public boolean isAsync() {
+ return async;
+ }
+
+ public void setAsync(boolean async) {
+ this.async = async;
+ }
+
+ public String getHttpVersion() {
+ return httpVersion;
+ }
+
+ public void setHttpVersion(String httpVersion) {
+ this.httpVersion = httpVersion;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java
b/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java
index ce5d42aa7a..9099c82977 100644
---
a/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java
+++
b/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java
@@ -18,18 +18,23 @@ package org.apache.seata.core.protocol.detector;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http2.Http2FrameCodecBuilder;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
import io.netty.handler.codec.http2.Http2MultiplexHandler;
import io.netty.handler.codec.http2.Http2StreamChannel;
import io.netty.util.CharsetUtil;
import org.apache.seata.core.rpc.netty.grpc.GrpcDecoder;
import org.apache.seata.core.rpc.netty.grpc.GrpcEncoder;
+import org.apache.seata.core.rpc.netty.http.Http2HttpHandler;
public class Http2Detector implements ProtocolDetector {
private static final byte[] HTTP2_PREFIX_BYTES = "PRI *
HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(CharsetUtil.UTF_8);
- private ChannelHandler[] serverHandlers;
+ private final ChannelHandler[] serverHandlers;
public Http2Detector(ChannelHandler[] serverHandlers) {
this.serverHandlers = serverHandlers;
@@ -55,12 +60,29 @@ public class Http2Detector implements ProtocolDetector {
new Http2MultiplexHandler(new
ChannelInitializer<Http2StreamChannel>() {
@Override
protected void initChannel(Http2StreamChannel ch) {
- final ChannelPipeline p = ch.pipeline();
+ ch.pipeline().addLast(new Http2SelectorHandler());
+ }
+ })
+ };
+ }
+
+ private class Http2SelectorHandler extends ChannelInboundHandlerAdapter {
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) {
+ if (msg instanceof Http2HeadersFrame) {
+ Http2HeadersFrame headersFrame = (Http2HeadersFrame) msg;
+ CharSequence contentType =
headersFrame.headers().get(HttpHeaderNames.CONTENT_TYPE);
+ final ChannelPipeline p = ctx.pipeline();
+ if (contentType != null &&
contentType.toString().endsWith("grpc")) {
p.addLast(new GrpcDecoder());
p.addLast(new GrpcEncoder());
p.addLast(serverHandlers);
+ } else {
+ p.addLast(new Http2HttpHandler());
}
- })
- };
+ p.remove(this);
+ }
+ ctx.fireChannelRead(msg);
+ }
}
}
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/detector/HttpDetector.java
b/core/src/main/java/org/apache/seata/core/protocol/detector/HttpDetector.java
index 2b6cf03256..0bdadb8f4e 100644
---
a/core/src/main/java/org/apache/seata/core/protocol/detector/HttpDetector.java
+++
b/core/src/main/java/org/apache/seata/core/protocol/detector/HttpDetector.java
@@ -18,11 +18,26 @@ package org.apache.seata.core.protocol.detector;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+import io.netty.handler.codec.http2.Http2FrameCodecBuilder;
+import io.netty.handler.codec.http2.Http2MultiplexHandler;
+import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
+import io.netty.handler.codec.http2.Http2StreamChannel;
+import io.netty.util.AsciiString;
+import org.apache.seata.core.rpc.netty.http.Http2HttpHandler;
import org.apache.seata.core.rpc.netty.http.HttpDispatchHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class HttpDetector implements ProtocolDetector {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(HttpDetector.class);
private static final String[] HTTP_METHODS = {"GET", "POST", "PUT",
"DELETE", "HEAD", "OPTIONS", "PATCH"};
@Override
@@ -52,7 +67,59 @@ public class HttpDetector implements ProtocolDetector {
@Override
public ChannelHandler[] getHandlers() {
- return new ChannelHandler[] {new HttpServerCodec(), new
HttpObjectAggregator(1048576), new HttpDispatchHandler()
+ HttpServerCodec sourceCodec = new HttpServerCodec();
+ HttpServerUpgradeHandler upgradeHandler =
getHttpServerUpgradeHandler(sourceCodec);
+
+ ChannelInboundHandlerAdapter upgradeCleanupHandler = new
ChannelInboundHandlerAdapter() {
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object
evt) throws Exception {
+ if (evt instanceof HttpServerUpgradeHandler.UpgradeEvent) {
+ ChannelPipeline p = ctx.pipeline();
+ p.remove(HttpObjectAggregator.class);
+ p.remove(HttpDispatchHandler.class);
+ }
+ super.userEventTriggered(ctx, evt);
+ }
+ };
+
+ ChannelInboundHandlerAdapter finalExceptionHandler = new
ChannelInboundHandlerAdapter() {
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable
cause) {
+ if (cause instanceof java.io.IOException) {
+ LOGGER.trace("Connection closed by client: {}",
cause.getMessage());
+ } else {
+ LOGGER.error("Exception caught in HTTP pipeline: ", cause);
+ }
+ ctx.close();
+ }
+ };
+
+ return new ChannelHandler[] {
+ sourceCodec,
+ upgradeHandler,
+ upgradeCleanupHandler,
+ new HttpObjectAggregator(1048576),
+ new HttpDispatchHandler(),
+ finalExceptionHandler
+ };
+ }
+
+ private static HttpServerUpgradeHandler
getHttpServerUpgradeHandler(HttpServerCodec sourceCodec) {
+ HttpServerUpgradeHandler.UpgradeCodecFactory upgradeCodecFactory =
protocol -> {
+ if
(AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME,
protocol)) {
+ return new Http2ServerUpgradeCodec(
+ Http2FrameCodecBuilder.forServer().build(),
+ new Http2MultiplexHandler(new
ChannelInitializer<Http2StreamChannel>() {
+ @Override
+ protected void initChannel(Http2StreamChannel ch) {
+ ch.pipeline().addLast(new Http2HttpHandler());
+ }
+ }));
+ } else {
+ return null;
+ }
};
+
+ return new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory,
1048576);
}
}
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
new file mode 100644
index 0000000000..995b4efb5f
--- /dev/null
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.ObjectMapper;
+import io.netty.channel.SimpleChannelInboundHandler;
+import org.apache.seata.common.thread.NamedThreadFactory;
+import org.apache.seata.core.rpc.netty.NettyServerConfig;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public abstract class BaseHttpChannelHandler<T> extends
SimpleChannelInboundHandler<T> {
+
+ protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ /**
+ * HTTP request processing thread pool, independent of Netty IO threads,
to avoid blocking network processing.
+ */
+ protected static final ExecutorService HTTP_HANDLER_THREADS = new
ThreadPoolExecutor(
+ NettyServerConfig.getMinHttpPoolSize(),
+ NettyServerConfig.getMaxHttpPoolSize(),
+ NettyServerConfig.getHttpKeepAliveTime(),
+ TimeUnit.SECONDS,
+ new
LinkedBlockingQueue<>(NettyServerConfig.getMaxHttpTaskQueueSize()),
+ new NamedThreadFactory("HTTPHandlerThread",
NettyServerConfig.getMaxHttpPoolSize()),
+ new ThreadPoolExecutor.AbortPolicy());
+
+ static {
+ Runtime.getRuntime().addShutdownHook(new
Thread(HTTP_HANDLER_THREADS::shutdown));
+ }
+}
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
new file mode 100644
index 0000000000..5dc3f813a9
--- /dev/null
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
@@ -0,0 +1,199 @@
+/*
+ * 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.node.ObjectNode;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+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;
+import io.netty.handler.codec.http2.Http2DataFrame;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
+import io.netty.handler.codec.http2.Http2StreamFrame;
+import org.apache.seata.common.rpc.http.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The http2 http handler.
+ */
+public class Http2HttpHandler extends BaseHttpChannelHandler<Http2StreamFrame>
{
+ private static final Logger LOGGER =
LoggerFactory.getLogger(Http2HttpHandler.class);
+ private Http2Headers http2Headers;
+ private ByteBuf bodyBuffer;
+ private boolean headersEndStream = false;
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame
msg) throws Exception {
+ if (bodyBuffer == null) {
+ bodyBuffer = ctx.alloc().buffer();
+ }
+ try {
+ if (msg instanceof Http2HeadersFrame) {
+ Http2HeadersFrame headersFrame = (Http2HeadersFrame) msg;
+ this.http2Headers = headersFrame.headers();
+ headersEndStream = headersFrame.isEndStream();
+ if (headersEndStream) {
+ handleRequest(ctx);
+ }
+ } else if (msg instanceof Http2DataFrame) {
+ Http2DataFrame dataFrame = (Http2DataFrame) msg;
+ bodyBuffer.writeBytes(dataFrame.content());
+ if (dataFrame.isEndStream()) {
+ handleRequest(ctx);
+ }
+ }
+ } catch (Exception e) {
+ if (bodyBuffer != null) {
+ bodyBuffer.release();
+ bodyBuffer = null;
+ }
+ throw e;
+ }
+ }
+
+ private void handleRequest(ChannelHandlerContext ctx) {
+ try {
+ if (http2Headers == null || http2Headers.method() == null ||
http2Headers.path() == null) {
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST);
+ return;
+ }
+ 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);
+
+ // reuse HttpDispatchHandler logic
+ boolean keepAlive = true; // In HTTP/2, connections are persistent
by default
+ QueryStringDecoder queryStringDecoder = new
QueryStringDecoder(request.getPath());
+ String requestPath = queryStringDecoder.path();
+ 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);
+ ObjectNode requestDataNode = OBJECT_MAPPER.createObjectNode();
+ requestDataNode.set("param",
ParameterParser.convertParamMap(queryStringDecoder.parameters()));
+ if (request.getMethod() == HttpMethod.POST
+ && request.getBody() != null
+ && !request.getBody().isEmpty()) {
+ // assume body is json
+ try {
+ ObjectNode bodyDataNode = (ObjectNode)
OBJECT_MAPPER.readTree(request.getBody());
+ requestDataNode.set("body", bodyDataNode);
+ } catch (Exception e) {
+ LOGGER.warn("Failed to parse http2 body as json: {}",
e.getMessage());
+ }
+ }
+ Object httpController = httpInvocation.getController();
+ Method handleMethod = httpInvocation.getMethod();
+ Object[] args = ParameterParser.getArgValues(
+ httpInvocation.getParamMetaData(), handleMethod,
requestDataNode, httpContext);
+ handle(httpController, handleMethod, args, ctx, httpContext);
+ } catch (Exception e) {
+ LOGGER.error("Exception occurred while processing HTTP2 request:
{}", e.getMessage(), e);
+ sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
+ } finally {
+ if (bodyBuffer != null) {
+ bodyBuffer.release();
+ bodyBuffer = null;
+ }
+ http2Headers = null;
+ headersEndStream = false;
+ }
+ }
+
+ 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 sendResponse(ChannelHandlerContext ctx, Object result) throws
Exception {
+ byte[] body = result != null ? OBJECT_MAPPER.writeValueAsBytes(result)
: new byte[0];
+ Http2Headers headers = new
DefaultHttp2Headers().status(HttpResponseStatus.OK.codeAsText());
+ headers.set(HttpHeaderNames.CONTENT_TYPE, "application/json;
charset=UTF-8");
+ headers.set(HttpHeaderNames.CONTENT_LENGTH,
String.valueOf(body.length));
+
+ ctx.write(new DefaultHttp2HeadersFrame(headers));
+ if (body.length > 0) {
+ ByteBuf content = Unpooled.wrappedBuffer(body);
+ ctx.write(new DefaultHttp2DataFrame(content, true));
+ } else {
+ ctx.write(new DefaultHttp2DataFrame(Unpooled.EMPTY_BUFFER, true));
+ }
+ ctx.flush();
+ }
+
+ private void sendErrorResponse(ChannelHandlerContext ctx,
HttpResponseStatus status) {
+ Http2Headers headers = new
DefaultHttp2Headers().status(status.codeAsText());
+ ctx.writeAndFlush(new DefaultHttp2HeadersFrame(headers, true));
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ // This is a common exception when the client (like curl) closes the
connection after receiving the response.
+ // We can safely ignore it by simply closing the context.
+ if (cause instanceof java.io.IOException) {
+ LOGGER.trace("Client connection closed: {}", cause.getMessage());
+ } else {
+ LOGGER.error("Exception caught in Http2HttpHandler: ", cause);
+ }
+ ctx.close();
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ try {
+ if (bodyBuffer != null) {
+ bodyBuffer.release();
+ bodyBuffer = null;
+ }
+ } finally {
+ super.channelInactive(ctx);
+ }
+ }
+}
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 23d28bc066..0baa5c47fe 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
@@ -17,12 +17,10 @@
package org.apache.seata.core.rpc.netty.http;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
@@ -36,41 +34,18 @@ 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.common.thread.NamedThreadFactory;
-import org.apache.seata.core.rpc.netty.NettyServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
/**
* A Netty HTTP request handler that dispatches incoming requests to
corresponding controller methods
*/
-public class HttpDispatchHandler extends
SimpleChannelInboundHandler<HttpRequest> {
+public class HttpDispatchHandler extends BaseHttpChannelHandler<HttpRequest> {
private static final Logger LOGGER =
LoggerFactory.getLogger(HttpDispatchHandler.class);
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
- /**
- * HTTP request processing thread pool, independent of Netty IO threads,
to avoid blocking network processing.
- */
- private static final ExecutorService HTTP_HANDLER_THREADS = new
ThreadPoolExecutor(
- NettyServerConfig.getMinHttpPoolSize(),
- NettyServerConfig.getMaxHttpPoolSize(),
- NettyServerConfig.getHttpKeepAliveTime(),
- TimeUnit.SECONDS,
- new
LinkedBlockingQueue<>(NettyServerConfig.getMaxHttpTaskQueueSize()),
- new NamedThreadFactory("HTTPHandlerThread",
NettyServerConfig.getMaxHttpPoolSize()),
- new ThreadPoolExecutor.AbortPolicy());
-
- static {
- Runtime.getRuntime().addShutdownHook(new
Thread(HTTP_HANDLER_THREADS::shutdown));
- }
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest
httpRequest) {
@@ -82,13 +57,13 @@ public class HttpDispatchHandler extends
SimpleChannelInboundHandler<HttpRequest
HttpInvocation httpInvocation =
ControllerManager.getHttpInvocation(path);
if (httpInvocation == null) {
- sendErrorResponse(ctx, HttpResponseStatus.NOT_FOUND,
keepAlive);
+ sendErrorResponse(ctx, HttpResponseStatus.NOT_FOUND, false);
return;
}
- HttpContext httpContext = new HttpContext(httpRequest, ctx,
keepAlive);
+ HttpContext<HttpRequest> httpContext = new
HttpContext<>(httpRequest, ctx, keepAlive, HttpContext.HTTP_1_1);
ObjectNode requestDataNode = OBJECT_MAPPER.createObjectNode();
- requestDataNode.putIfAbsent("param",
ParameterParser.convertParamMap(queryStringDecoder.parameters()));
+ requestDataNode.set("param",
ParameterParser.convertParamMap(queryStringDecoder.parameters()));
if (httpRequest.method() == HttpMethod.POST) {
HttpPostRequestDecoder httpPostRequestDecoder = null;
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
new file mode 100644
index 0000000000..fe31fb6b7d
--- /dev/null
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/SimpleHttp2Request.java
@@ -0,0 +1,50 @@
+/*
+ * 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 io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http2.Http2Headers;
+
+public class SimpleHttp2Request {
+ private final HttpMethod method;
+ private final String path;
+ private final Http2Headers headers;
+ private final String body;
+
+ public SimpleHttp2Request(HttpMethod method, String path, Http2Headers
headers, String body) {
+ this.method = method;
+ this.path = path;
+ this.headers = headers;
+ this.body = body;
+ }
+
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Http2Headers getHeaders() {
+ return headers;
+ }
+
+ public String getBody() {
+ return body;
+ }
+}
diff --git
a/core/src/test/java/org/apache/seata/core/protocol/detector/Http2DetectorTest.java
b/core/src/test/java/org/apache/seata/core/protocol/detector/Http2DetectorTest.java
new file mode 100644
index 0000000000..0f9bb1a151
--- /dev/null
+++
b/core/src/test/java/org/apache/seata/core/protocol/detector/Http2DetectorTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.protocol.detector;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandler;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class Http2DetectorTest {
+ @Test
+ void testDetectWithHttp2Prefix() {
+ byte[] http2Prefix = "PRI *
HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.UTF_8);
+ ByteBuf buf = Unpooled.copiedBuffer(http2Prefix);
+ Http2Detector detector = new Http2Detector(new ChannelHandler[] {});
+ assertTrue(detector.detect(buf));
+ buf.release();
+ }
+
+ @Test
+ void testDetectWithNonHttp2() {
+ ByteBuf buf = Unpooled.copiedBuffer("NOTHTTP2",
StandardCharsets.UTF_8);
+ Http2Detector detector = new Http2Detector(new ChannelHandler[] {});
+ assertFalse(detector.detect(buf));
+ buf.release();
+ }
+
+ @Test
+ void testDetectWithShortBuffer() {
+ ByteBuf buf = Unpooled.copiedBuffer("PRI * HTTP/2.0",
StandardCharsets.UTF_8);
+ Http2Detector detector = new Http2Detector(new ChannelHandler[] {});
+ assertFalse(detector.detect(buf));
+ buf.release();
+ }
+
+ @Test
+ void testGetHandlersNotNull() {
+ ChannelHandler mockHandler = mock(ChannelHandler.class);
+ Http2Detector detector = new Http2Detector(new ChannelHandler[]
{mockHandler});
+ ChannelHandler[] handlers = detector.getHandlers();
+ assertNotNull(handlers);
+ assertEquals(2, handlers.length);
+ assertNotNull(handlers[0]);
+ assertNotNull(handlers[1]);
+ }
+}
diff --git
a/core/src/test/java/org/apache/seata/core/protocol/detector/HttpDetectorTest.java
b/core/src/test/java/org/apache/seata/core/protocol/detector/HttpDetectorTest.java
index f4bc1350ad..b75f45b7dc 100644
---
a/core/src/test/java/org/apache/seata/core/protocol/detector/HttpDetectorTest.java
+++
b/core/src/test/java/org/apache/seata/core/protocol/detector/HttpDetectorTest.java
@@ -19,14 +19,19 @@ package org.apache.seata.core.protocol.detector;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler;
import org.apache.seata.core.rpc.netty.http.HttpDispatchHandler;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
class HttpDetectorTest {
private final HttpDetector httpDetector = new HttpDetector();
@@ -55,13 +60,93 @@ class HttpDetectorTest {
@Test
void testGetHandlers() {
ChannelHandler[] handlers = httpDetector.getHandlers();
- assertEquals(3, handlers.length);
+ assertEquals(6, handlers.length);
assertInstanceOf(HttpServerCodec.class, handlers[0]);
- assertInstanceOf(HttpObjectAggregator.class, handlers[1]);
- assertInstanceOf(HttpDispatchHandler.class, handlers[2]);
+ assertInstanceOf(HttpServerUpgradeHandler.class, handlers[1]);
+ assertInstanceOf(ChannelHandler.class, handlers[2]); //
upgradeCleanupHandler
+ assertInstanceOf(HttpObjectAggregator.class, handlers[3]);
+ assertInstanceOf(HttpDispatchHandler.class, handlers[4]);
+ assertInstanceOf(ChannelHandler.class, handlers[5]); //
finalExceptionHandler
// Verify aggregator size
- HttpObjectAggregator aggregator = (HttpObjectAggregator) handlers[1];
+ HttpObjectAggregator aggregator = (HttpObjectAggregator) handlers[3];
assertEquals(1048576, aggregator.maxContentLength());
}
+
+ @Test
+ void testDetectWithShortBuffer() {
+ ByteBuf buf = Unpooled.copiedBuffer("GET", StandardCharsets.UTF_8);
+ assertFalse(httpDetector.detect(buf));
+ buf.release();
+ }
+
+ @Test
+ void testStartsWithNegativeBranch() {
+ ByteBuf buf = Unpooled.copiedBuffer("GE", StandardCharsets.UTF_8);
+ try {
+ java.lang.reflect.Method m =
+ HttpDetector.class.getDeclaredMethod("startsWith",
ByteBuf.class, String.class);
+ m.setAccessible(true);
+ boolean result = (boolean) m.invoke(httpDetector, buf, "GET");
+ assertFalse(result);
+ } catch (Exception e) {
+ fail(e);
+ }
+ buf.release();
+ }
+
+ @Test
+ void testUpgradeHandlerNonHttp2() {
+ try {
+ java.lang.reflect.Method m = HttpDetector.class.getDeclaredMethod(
+ "getHttpServerUpgradeHandler",
io.netty.handler.codec.http.HttpServerCodec.class);
+ m.setAccessible(true);
+ io.netty.handler.codec.http.HttpServerCodec codec = new
io.netty.handler.codec.http.HttpServerCodec();
+ Object handler = m.invoke(null, codec);
+ assertNotNull(handler);
+ assertTrue(handler instanceof
io.netty.handler.codec.http.HttpServerUpgradeHandler);
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ @Test
+ void testUpgradeCleanupHandlerEvent() throws Exception {
+ ChannelHandler[] handlers = httpDetector.getHandlers();
+ ChannelInboundHandlerAdapter upgradeCleanupHandler =
(ChannelInboundHandlerAdapter) handlers[2];
+ HttpServerUpgradeHandler.UpgradeEvent mockEvent =
mock(HttpServerUpgradeHandler.UpgradeEvent.class);
+ ChannelHandlerContext mockCtx = mock(ChannelHandlerContext.class);
+ ChannelPipeline mockPipeline = mock(ChannelPipeline.class);
+ when(mockCtx.pipeline()).thenReturn(mockPipeline);
+ upgradeCleanupHandler.userEventTriggered(mockCtx, mockEvent);
+ verify(mockPipeline).remove(HttpObjectAggregator.class);
+ verify(mockPipeline).remove(HttpDispatchHandler.class);
+ }
+
+ @Test
+ void testUpgradeCleanupHandlerNonUpgradeEvent() throws Exception {
+ ChannelHandler[] handlers = httpDetector.getHandlers();
+ ChannelHandlerContext mockCtx = mock(ChannelHandlerContext.class);
+ ChannelInboundHandlerAdapter upgradeCleanupHandler =
(ChannelInboundHandlerAdapter) handlers[2];
+ upgradeCleanupHandler.userEventTriggered(mockCtx, "not-upgrade-event");
+ }
+
+ @Test
+ void testFinalExceptionHandler() throws Exception {
+ ChannelHandler[] handlers = httpDetector.getHandlers();
+ ChannelHandler finalExceptionHandler = handlers[5];
+ ChannelHandlerContext mockCtx = mock(ChannelHandlerContext.class);
+ finalExceptionHandler.exceptionCaught(mockCtx, new
java.io.IOException("test"));
+ finalExceptionHandler.exceptionCaught(mockCtx, new
RuntimeException("test"));
+ verify(mockCtx, times(2)).close();
+ }
+
+ @Test
+ void testFinalExceptionHandlerNonIOException() throws Exception {
+ ChannelHandler[] handlers = httpDetector.getHandlers();
+ ChannelInboundHandlerAdapter finalExceptionHandler =
(ChannelInboundHandlerAdapter) handlers[5];
+ ChannelHandlerContext mockCtx = mock(ChannelHandlerContext.class);
+ finalExceptionHandler.exceptionCaught(mockCtx, new
IllegalArgumentException("test"));
+ verify(mockCtx).close();
+ }
}
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
new file mode 100644
index 0000000000..c4ca1e0dda
--- /dev/null
+++
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.ObjectMapper;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
+import io.netty.handler.codec.http2.Http2StreamFrame;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class Http2HttpHandlerTest {
+ private Http2HttpHandler handler;
+ private EmbeddedChannel channel;
+ private TestController testController = new TestController();
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ static class TestController {
+ public String handleRequest(String param) {
+ return "Processed: " + param;
+ }
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+ handler = new Http2HttpHandler();
+ channel = new EmbeddedChannel(handler);
+ Method method = TestController.class.getMethod("handleRequest",
String.class);
+ ParamMetaData paramMetaData = new ParamMetaData();
+
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
+ paramMetaData.setParamName("param");
+ ParamMetaData[] paramMetaDatas = new ParamMetaData[] {paramMetaData};
+ HttpInvocation invocation = new HttpInvocation();
+ invocation.setController(testController);
+ invocation.setMethod(method);
+ invocation.setPath("/test");
+ invocation.setParamMetaData(paramMetaDatas);
+ ControllerManager.addHttpInvocation(invocation);
+ }
+
+ private Http2StreamFrame waitForHttp2Response(long timeoutMs) {
+ long startTime = System.currentTimeMillis();
+ Http2StreamFrame response = null;
+ while (response == null && (System.currentTimeMillis() - startTime) <
timeoutMs) {
+ response = channel.readOutbound();
+ if (response == null) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while waiting for
response", e);
+ }
+ }
+ }
+ return response;
+ }
+
+ @Test
+ void testHttp2GetRequestWithParameters() throws Exception {
+ 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
+ void testHttp2RequestToNonexistentPath() {
+ Http2Headers headers = new DefaultHttp2Headers();
+ headers.method("GET");
+ headers.path("/notfound");
+ Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers,
true);
+ channel.writeInbound(headersFrame);
+
+ Http2StreamFrame responseHeadersFrame = channel.readOutbound();
+ assertTrue(responseHeadersFrame instanceof DefaultHttp2HeadersFrame);
+ DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame)
responseHeadersFrame;
+ assertEquals("404", respHeaders.headers().status().toString());
+ }
+
+ @Test
+ void testHttp2PostRequestWithJsonBody() throws Exception {
+ 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);
+
+ Http2StreamFrame frame1 = null, frame2 = null;
+ long deadline = System.currentTimeMillis() + 5000; // 最多等5秒
+ 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
+ void testHttp2BadRequest() {
+ Http2Headers headers = new DefaultHttp2Headers();
+ Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers,
true);
+ channel.writeInbound(headersFrame);
+ Http2StreamFrame responseHeadersFrame = channel.readOutbound();
+ assertTrue(responseHeadersFrame instanceof DefaultHttp2HeadersFrame);
+ DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame)
responseHeadersFrame;
+ assertEquals("400", respHeaders.headers().status().toString());
+ }
+
+ @org.junit.jupiter.api.AfterEach
+ void tearDown() throws Exception {
+ // Clean up ControllerManager
+ Field field =
ControllerManager.class.getDeclaredField("HTTP_CONTROLLER_MAP");
+ field.setAccessible(true);
+ Map<String, HttpInvocation> map = (Map<String, HttpInvocation>)
field.get(null);
+ map.clear();
+ }
+}
diff --git
a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterWatcherManager.java
b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterWatcherManager.java
index c3ac1bd5ea..81b75ec716 100644
---
a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterWatcherManager.java
+++
b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterWatcherManager.java
@@ -49,7 +49,7 @@ public class ClusterWatcherManager implements
ClusterChangeListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
- private static final Map<String, Queue<Watcher<?>>> WATCHERS = new
ConcurrentHashMap<>();
+ private static final Map<String, Queue<Watcher<HttpContext>>> WATCHERS =
new ConcurrentHashMap<>();
private static final Map<String, Long> GROUP_UPDATE_TIME = new
ConcurrentHashMap<>();
@@ -92,13 +92,13 @@ public class ClusterWatcherManager implements
ClusterChangeListener {
}
}
- private void notifyWatcher(Watcher<?> watcher) {
+ private void notifyWatcher(Watcher<HttpContext> watcher) {
watcher.setDone(true);
sendWatcherResponse(watcher, HttpResponseStatus.OK);
}
- private void sendWatcherResponse(Watcher<?> watcher, HttpResponseStatus
nettyStatus) {
- Object context = watcher.getAsyncContext();
+ private void sendWatcherResponse(Watcher<HttpContext> watcher,
HttpResponseStatus nettyStatus) {
+ HttpContext context = watcher.getAsyncContext();
if (!(context instanceof HttpContext)) {
logger.warn(
"Unsupported context type for watcher on group {}: {}",
@@ -106,25 +106,27 @@ public class ClusterWatcherManager implements
ClusterChangeListener {
context != null ? context.getClass().getName() : "null");
return;
}
- HttpContext httpContext = (HttpContext) context;
- ChannelHandlerContext ctx = httpContext.getContext();
- if (ctx.channel().isActive()) {
- HttpResponse response =
- new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
nettyStatus, Unpooled.EMPTY_BUFFER);
- response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
+ ChannelHandlerContext ctx = context.getContext();
+ if (!context.isHttp2()) {
+ if (ctx.channel().isActive()) {
+ HttpResponse response =
+ new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
nettyStatus, Unpooled.EMPTY_BUFFER);
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
- if (!httpContext.isKeepAlive()) {
-
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+ if (!context.isKeepAlive()) {
+
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+ } else {
+ ctx.writeAndFlush(response);
+ }
} else {
- ctx.writeAndFlush(response);
+ logger.warn(
+ "Netty channel is not active for watcher on group {},
cannot send response.",
+ watcher.getGroup());
}
- } else {
- logger.warn(
- "Netty channel is not active for watcher on group {},
cannot send response.", watcher.getGroup());
}
}
- public void registryWatcher(Watcher<?> watcher) {
+ public void registryWatcher(Watcher<HttpContext> watcher) {
String group = watcher.getGroup();
Long term = GROUP_UPDATE_TIME.get(group);
if (term == null || watcher.getTerm() >= term) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]