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 7e28b3d1e7 optimize:enhance HttpClient to support h2c (#7740)
7e28b3d1e7 is described below

commit 7e28b3d1e7314bfd87e455adbac1897bd72d9352
Author: xiaoyu <[email protected]>
AuthorDate: Thu Oct 30 09:51:18 2025 +0800

    optimize:enhance HttpClient to support h2c (#7740)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 common/pom.xml                                     |   1 +
 .../apache/seata/common/util/Http5ClientUtil.java  | 155 -------------
 .../apache/seata/common/util/HttpClientUtil.java   | 167 +++++++++++++-
 .../seata/common/util/Http5ClientUtilTest.java     | 252 ---------------------
 .../seata/common/util/HttpClientUtilTest.java      | 220 ++++++++++++++++++
 .../seata/config/nacos/NacosConfigurationTest.java |   2 -
 discovery/seata-discovery-namingserver/pom.xml     |   4 +
 discovery/seata-discovery-raft/pom.xml             |   4 +
 namingserver/pom.xml                               |   4 +
 .../db/SqlServerResourceIdInitializer.java         |   2 -
 12 files changed, 393 insertions(+), 420 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index bca9ba55a6..6df7fab7ae 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -69,6 +69,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7722](https://github.com/apache/incubator-seata/pull/7722)] optimize 
serializer type meaning
 - [[#7741](https://github.com/apache/incubator-seata/pull/7741)] supports 
publishing image based on JDK 25
 - [[#7743](https://github.com/seata/seata/pull/7743)] upgrade Apache Tomcat 
dependency from 9.0.108 to 9.0.109
+- [[#7740](https://github.com/apache/incubator-seata/pull/7740)] enhance 
HttpClient to support h2c
 
 
 
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 5b7b2a3a49..83dfbf91f6 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -69,6 +69,7 @@
 - [[#7741](https://github.com/apache/incubator-seata/pull/7741)] 支持发布基于JDK 
25的镜像
 - [[#7743](https://github.com/seata/seata/pull/7743)] 将 Apache Tomcat 依赖项从 
9.0.108 升级到 9.0.109
 
+- [[#7740](https://github.com/apache/incubator-seata/pull/7740)] 
优化http工具类使之支持h2c协议
 
 
 ### security:
diff --git a/common/pom.xml b/common/pom.xml
index 2d2d047e8e..8a1cfffea8 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -55,6 +55,7 @@
         <dependency>
             <groupId>com.squareup.okhttp3</groupId>
             <artifactId>okhttp</artifactId>
+            <scope>provided</scope>
         </dependency>
     </dependencies>
 </project>
diff --git 
a/common/src/main/java/org/apache/seata/common/util/Http5ClientUtil.java 
b/common/src/main/java/org/apache/seata/common/util/Http5ClientUtil.java
deleted file mode 100644
index bcf95a00e5..0000000000
--- a/common/src/main/java/org/apache/seata/common/util/Http5ClientUtil.java
+++ /dev/null
@@ -1,155 +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.common.util;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import okhttp3.Call;
-import okhttp3.Callback;
-import okhttp3.FormBody;
-import okhttp3.Headers;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import org.apache.seata.common.executor.HttpCallback;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-public class Http5ClientUtil {
-
-    private static final Logger LOGGER = 
LoggerFactory.getLogger(Http5ClientUtil.class);
-
-    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
-    private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
-            .connectTimeout(10, TimeUnit.SECONDS)
-            .readTimeout(10, TimeUnit.SECONDS)
-            .writeTimeout(10, TimeUnit.SECONDS)
-            .build();
-
-    public static final MediaType MEDIA_TYPE_JSON = 
MediaType.parse("application/json");
-    public static final MediaType MEDIA_TYPE_FORM_URLENCODED = 
MediaType.parse("application/x-www-form-urlencoded");
-
-    public static void doPostHttp(
-            String url, Map<String, String> params, Map<String, String> 
headers, HttpCallback<Response> callback) {
-        try {
-            Headers.Builder headerBuilder = new Headers.Builder();
-            if (headers != null) {
-                headers.forEach(headerBuilder::add);
-            }
-
-            String contentType = headers != null ? headers.get("Content-Type") 
: "";
-            RequestBody requestBody = createRequestBody(params, contentType);
-
-            Request request = new Request.Builder()
-                    .url(url)
-                    .headers(headerBuilder.build())
-                    .post(requestBody)
-                    .build();
-
-            executeAsync(HTTP_CLIENT, request, callback);
-
-        } catch (JsonProcessingException e) {
-            LOGGER.error(e.getMessage(), e);
-            callback.onFailure(e);
-        }
-    }
-
-    public static void doPostHttp(
-            String url, String body, Map<String, String> headers, 
HttpCallback<Response> callback) {
-        Headers.Builder headerBuilder = new Headers.Builder();
-        if (headers != null) {
-            headers.forEach(headerBuilder::add);
-        }
-
-        RequestBody requestBody = RequestBody.create(body, MEDIA_TYPE_JSON);
-
-        Request request = new Request.Builder()
-                .url(url)
-                .headers(headerBuilder.build())
-                .post(requestBody)
-                .build();
-
-        executeAsync(HTTP_CLIENT, request, callback);
-    }
-
-    public static void doGetHttp(
-            String url, Map<String, String> headers, final 
HttpCallback<Response> callback, int timeout) {
-        OkHttpClient client = new OkHttpClient.Builder()
-                .connectTimeout(timeout, TimeUnit.SECONDS)
-                .readTimeout(timeout, TimeUnit.SECONDS)
-                .writeTimeout(timeout, TimeUnit.SECONDS)
-                .build();
-
-        Headers.Builder headerBuilder = new Headers.Builder();
-        if (headers != null) {
-            headers.forEach(headerBuilder::add);
-        }
-
-        Request request = new Request.Builder()
-                .url(url)
-                .headers(headerBuilder.build())
-                .get()
-                .build();
-
-        executeAsync(client, request, callback);
-    }
-
-    private static RequestBody createRequestBody(Map<String, String> params, 
String contentType)
-            throws JsonProcessingException {
-        if (params == null || params.isEmpty()) {
-            return RequestBody.create(new byte[0]);
-        }
-
-        if (MEDIA_TYPE_FORM_URLENCODED.toString().equals(contentType)) {
-            FormBody.Builder formBuilder = new FormBody.Builder();
-            params.forEach(formBuilder::add);
-            return formBuilder.build();
-        } else {
-            String json = OBJECT_MAPPER.writeValueAsString(params);
-            return RequestBody.create(json, MEDIA_TYPE_JSON);
-        }
-    }
-
-    private static void executeAsync(OkHttpClient client, Request request, 
final HttpCallback<Response> callback) {
-        client.newCall(request).enqueue(new Callback() {
-            @Override
-            public void onResponse(Call call, Response response) {
-                try {
-                    callback.onSuccess(response);
-                } finally {
-                    response.close();
-                }
-            }
-
-            @Override
-            public void onFailure(Call call, IOException e) {
-                if (call.isCanceled()) {
-                    callback.onCancelled();
-                } else {
-                    callback.onFailure(e);
-                }
-            }
-        });
-    }
-}
diff --git 
a/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java 
b/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java
index 31a5f03ae3..5fcd499626 100644
--- a/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java
+++ b/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java
@@ -16,7 +16,18 @@
  */
 package org.apache.seata.common.util;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.FormBody;
+import okhttp3.Headers;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
 import org.apache.http.NameValuePair;
 import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.config.RequestConfig;
@@ -31,6 +42,7 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 import org.apache.http.message.BasicNameValuePair;
+import org.apache.seata.common.executor.HttpCallback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,9 +51,11 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 
 public class HttpClientUtil {
 
@@ -52,21 +66,44 @@ public class HttpClientUtil {
     private static final PoolingHttpClientConnectionManager 
POOLING_HTTP_CLIENT_CONNECTION_MANAGER =
             new PoolingHttpClientConnectionManager();
 
+    private static final Map<Integer /*timeout*/, OkHttpClient> 
HTTP2_CLIENT_MAP = new ConcurrentHashMap<>();
+
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
+    public static final MediaType MEDIA_TYPE_JSON = 
MediaType.parse("application/json");
+
+    public static final MediaType MEDIA_TYPE_FORM_URLENCODED = 
MediaType.parse("application/x-www-form-urlencoded");
+
     static {
         POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(10);
         POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(10);
-        Runtime.getRuntime().addShutdownHook(new Thread(() -> 
HTTP_CLIENT_MAP.values().parallelStream()
-                .forEach(client -> {
-                    try {
-                        // delay 3s, make sure unregister http request send 
successfully
-                        Thread.sleep(3000);
-                        client.close();
-                    } catch (IOException | InterruptedException e) {
-                        LOGGER.error(e.getMessage(), e);
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            HTTP_CLIENT_MAP.values().parallelStream().forEach(client -> {
+                try {
+                    // delay 3s, make sure unregister http request send 
successfully
+                    Thread.sleep(3000);
+                    client.close();
+                } catch (IOException | InterruptedException e) {
+                    LOGGER.error(e.getMessage(), e);
+                }
+            });
+
+            HTTP2_CLIENT_MAP.values().parallelStream().forEach(client -> {
+                try {
+                    client.dispatcher().executorService().shutdown();
+                    // Wait for up to 3 seconds for in-flight requests to 
complete
+                    if 
(!client.dispatcher().executorService().awaitTermination(3, TimeUnit.SECONDS)) {
+                        LOGGER.warn("Timeout waiting for OkHttp executor 
service to terminate.");
                     }
-                })));
+                    client.connectionPool().evictAll();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    LOGGER.error("Interrupted while waiting for OkHttp 
executor service to terminate.", e);
+                } catch (Exception e) {
+                    LOGGER.error(e.getMessage(), e);
+                }
+            });
+        }));
     }
 
     // post request
@@ -195,4 +232,116 @@ public class HttpClientUtil {
         CloseableHttpClient client = HttpClients.createDefault();
         return client.execute(post);
     }
+
+    public static void doPostWithHttp2(
+            String url, Map<String, String> params, Map<String, String> 
headers, HttpCallback<Response> callback) {
+        doPostWithHttp2(url, params, headers, callback, 10);
+    }
+
+    public static void doPostWithHttp2(
+            String url,
+            Map<String, String> params,
+            Map<String, String> headers,
+            HttpCallback<Response> callback,
+            int timeoutSeconds) {
+        try {
+            String contentType = headers != null ? headers.get("Content-Type") 
: "";
+            RequestBody requestBody = createRequestBody(params, contentType);
+            Request request = buildHttp2Request(url, headers, requestBody, 
"POST");
+            OkHttpClient client = createHttp2ClientWithTimeout(timeoutSeconds);
+            executeAsync(client, request, callback);
+        } catch (JsonProcessingException e) {
+            LOGGER.error(e.getMessage(), e);
+            callback.onFailure(e);
+        }
+    }
+
+    public static void doPostWithHttp2(
+            String url, String body, Map<String, String> headers, 
HttpCallback<Response> callback) {
+        // default timeout 10 seconds
+        doPostWithHttp2(url, body, headers, callback, 10);
+    }
+
+    public static void doPostWithHttp2(
+            String url, String body, Map<String, String> headers, 
HttpCallback<Response> callback, int timeoutSeconds) {
+        RequestBody requestBody = RequestBody.create(body, MEDIA_TYPE_JSON);
+        Request request = buildHttp2Request(url, headers, requestBody, "POST");
+        OkHttpClient client = createHttp2ClientWithTimeout(timeoutSeconds);
+        executeAsync(client, request, callback);
+    }
+
+    public static void doGetWithHttp2(
+            String url, Map<String, String> headers, final 
HttpCallback<Response> callback, int timeoutSeconds) {
+        Request request = buildHttp2Request(url, headers, null, "GET");
+        OkHttpClient client = createHttp2ClientWithTimeout(timeoutSeconds);
+        executeAsync(client, request, callback);
+    }
+
+    private static RequestBody createRequestBody(Map<String, String> params, 
String contentType)
+            throws JsonProcessingException {
+        if (params == null || params.isEmpty()) {
+            return RequestBody.create(new byte[0]);
+        }
+
+        // Extract media type without parameters for robust comparison
+        String mediaTypeOnly = contentType == null ? "" : 
contentType.split(";")[0].trim();
+        if (MEDIA_TYPE_FORM_URLENCODED.toString().equals(mediaTypeOnly)) {
+            FormBody.Builder formBuilder = new FormBody.Builder();
+            params.forEach(formBuilder::add);
+            return formBuilder.build();
+        } else {
+            String json = OBJECT_MAPPER.writeValueAsString(params);
+            return RequestBody.create(json, MEDIA_TYPE_JSON);
+        }
+    }
+
+    private static OkHttpClient createHttp2ClientWithTimeout(int 
timeoutSeconds) {
+        return HTTP2_CLIENT_MAP.computeIfAbsent(timeoutSeconds, k -> new 
OkHttpClient.Builder()
+                // Use HTTP/2 prior knowledge to directly use HTTP/2 without 
an initial HTTP/1.1 upgrade
+                
.protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE))
+                .connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
+                .readTimeout(timeoutSeconds, TimeUnit.SECONDS)
+                .writeTimeout(timeoutSeconds, TimeUnit.SECONDS)
+                .build());
+    }
+
+    private static Request buildHttp2Request(
+            String url, Map<String, String> headers, RequestBody requestBody, 
String method) {
+        Headers.Builder headerBuilder = new Headers.Builder();
+        if (headers != null) {
+            headers.forEach(headerBuilder::add);
+        }
+
+        Request.Builder requestBuilder = new 
Request.Builder().url(url).headers(headerBuilder.build());
+
+        if ("POST".equals(method) && requestBody != null) {
+            requestBuilder.post(requestBody);
+        } else if ("GET".equals(method)) {
+            requestBuilder.get();
+        }
+
+        return requestBuilder.build();
+    }
+
+    private static void executeAsync(OkHttpClient client, Request request, 
final HttpCallback<Response> callback) {
+        client.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onResponse(Call call, Response response) {
+                try {
+                    callback.onSuccess(response);
+                } finally {
+                    response.close();
+                }
+            }
+
+            @Override
+            public void onFailure(Call call, IOException e) {
+                if (call.isCanceled()) {
+                    callback.onCancelled();
+                } else {
+                    callback.onFailure(e);
+                }
+            }
+        });
+    }
 }
diff --git 
a/common/src/test/java/org/apache/seata/common/util/Http5ClientUtilTest.java 
b/common/src/test/java/org/apache/seata/common/util/Http5ClientUtilTest.java
deleted file mode 100644
index 9b550067e7..0000000000
--- a/common/src/test/java/org/apache/seata/common/util/Http5ClientUtilTest.java
+++ /dev/null
@@ -1,252 +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.common.util;
-
-import okhttp3.Protocol;
-import okhttp3.Response;
-import org.apache.seata.common.executor.HttpCallback;
-import org.junit.jupiter.api.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-class Http5ClientUtilTest {
-
-    @Test
-    void testDoPostHttp_param_onSuccess() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response result) {
-                assertNotNull(result);
-                assertEquals(Protocol.HTTP_2, result.protocol());
-                latch.countDown();
-            }
-
-            @Override
-            public void onFailure(Throwable e) {
-                fail("Should not fail");
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> params = new HashMap<>();
-        params.put("key", "value");
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Content-Type", "application/json");
-
-        Http5ClientUtil.doPostHttp("https://www.apache.org/";, params, headers, 
callback);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    void testDoPostHttp_param_onFailure() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response response) {
-                fail("Should not succeed");
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                assertNotNull(t);
-                latch.countDown();
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> params = new HashMap<>();
-        params.put("key", "value");
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Content-Type", "application/json");
-
-        Http5ClientUtil.doPostHttp("http://localhost:9999/invalid";, params, 
headers, callback);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    void testDoPostHttp_body_onSuccess() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response result) {
-                assertNotNull(result);
-                assertEquals(Protocol.HTTP_2, result.protocol());
-                latch.countDown();
-            }
-
-            @Override
-            public void onFailure(Throwable e) {
-                fail("Should not fail");
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Content-Type", "application/json");
-
-        Http5ClientUtil.doPostHttp("https://www.apache.org/";, 
"{\"key\":\"value\"}", headers, callback);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    void testDoPostHttp_body_onFailure() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response response) {
-                fail("Should not succeed");
-            }
-
-            @Override
-            public void onFailure(Throwable t) {
-                assertNotNull(t);
-                latch.countDown();
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Content-Type", "application/json");
-
-        Http5ClientUtil.doPostHttp("http://localhost:9999/invalid";, 
"{\"key\":\"value\"}", headers, callback);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    void testDoPostHttp_param_onSuccess_forceHttp1() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response result) {
-                assertNotNull(result);
-                assertEquals(Protocol.HTTP_1_1, result.protocol());
-                latch.countDown();
-            }
-
-            @Override
-            public void onFailure(Throwable e) {
-                fail("Should not fail");
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> params = new HashMap<>();
-        params.put("key", "value");
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Content-Type", "application/json");
-
-        Http5ClientUtil.doPostHttp("http://httpbin.org/post";, params, headers, 
callback);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    void testDoGetHttp_onSuccess() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response result) {
-                assertNotNull(result);
-                assertEquals(Protocol.HTTP_2, result.protocol());
-                latch.countDown();
-            }
-
-            @Override
-            public void onFailure(Throwable e) {
-                fail("Should not fail");
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Accept", "application/json");
-
-        Http5ClientUtil.doGetHttp("https://www.apache.org/";, headers, 
callback, 1);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-
-    @Test
-    void testDoPostHttp_body_onSuccess_forceHttp1() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        HttpCallback<Response> callback = new HttpCallback<Response>() {
-            @Override
-            public void onSuccess(Response result) {
-                assertNotNull(result);
-                assertEquals(Protocol.HTTP_1_1, result.protocol());
-                latch.countDown();
-            }
-
-            @Override
-            public void onFailure(Throwable e) {
-                fail("Should not fail");
-            }
-
-            @Override
-            public void onCancelled() {
-                fail("Should not be cancelled");
-            }
-        };
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("Content-Type", "application/json");
-
-        Http5ClientUtil.doPostHttp("http://httpbin.org/post";, 
"{\"key\":\"value\"}", headers, callback);
-        assertTrue(latch.await(10, TimeUnit.SECONDS));
-    }
-}
diff --git 
a/common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java 
b/common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java
index b20f1cbfce..6df6b2c8c0 100644
--- a/common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java
+++ b/common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java
@@ -16,11 +16,28 @@
  */
 package org.apache.seata.common.util;
 
+import okhttp3.OkHttpClient;
+import okhttp3.Response;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.seata.common.executor.HttpCallback;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 public class HttpClientUtilTest {
 
@@ -29,4 +46,207 @@ public class HttpClientUtilTest {
         Assertions.assertNull(HttpClientUtil.doPost("test", new HashMap<>(), 
new HashMap<>(), 0));
         Assertions.assertNull(HttpClientUtil.doGet("test", new HashMap<>(), 
new HashMap<>(), 0));
     }
+
+    @Test
+    void testDoPostWithHttp2_param_onFailure() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        HttpCallback<Response> callback = new HttpCallback<Response>() {
+            @Override
+            public void onSuccess(Response response) {
+                fail("Should not succeed");
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                assertNotNull(t);
+                latch.countDown();
+            }
+
+            @Override
+            public void onCancelled() {
+                fail("Should not be cancelled");
+            }
+        };
+
+        Map<String, String> params = new HashMap<>();
+        params.put("key", "value");
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", "application/json");
+
+        HttpClientUtil.doPostWithHttp2("http://localhost:9999/invalid";, 
params, headers, callback);
+        assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    void testDoPostWithHttp2_body_onFailure() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        HttpCallback<Response> callback = new HttpCallback<Response>() {
+            @Override
+            public void onSuccess(Response response) {
+                fail("Should not succeed");
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                assertNotNull(t);
+                latch.countDown();
+            }
+
+            @Override
+            public void onCancelled() {
+                fail("Should not be cancelled");
+            }
+        };
+
+        String body = "{\"key\":\"value\"}";
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", "application/json");
+
+        HttpClientUtil.doPostWithHttp2("http://localhost:9999/invalid";, body, 
headers, callback);
+        assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    void testDoPostWithHttp2_body_withCharset_onFailure() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        HttpCallback<Response> callback = new HttpCallback<Response>() {
+            @Override
+            public void onSuccess(Response response) {
+                fail("Should not succeed");
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                assertNotNull(t);
+                latch.countDown();
+            }
+
+            @Override
+            public void onCancelled() {
+                fail("Should not be cancelled");
+            }
+        };
+
+        Map<String, String> params = new HashMap<>();
+        params.put("key", "value");
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", 
"application/x-www-form-urlencoded;charset=UTF-8");
+
+        HttpClientUtil.doPostWithHttp2("http://localhost:9999/invalid";, 
params, headers, callback, 30000);
+        assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    void testDoPostWithHttp2_withEmptyParam_onFailure() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        HttpCallback<Response> callback = new HttpCallback<Response>() {
+            @Override
+            public void onSuccess(Response response) {
+                fail("Should not succeed");
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                assertNotNull(t);
+                latch.countDown();
+            }
+
+            @Override
+            public void onCancelled() {
+                fail("Should not be cancelled");
+            }
+        };
+
+        Map<String, String> params = new HashMap<>();
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", "application/json;charset=UTF-8");
+
+        HttpClientUtil.doPostWithHttp2("http://localhost:9999/invalid";, 
params, headers, callback, 30000);
+        assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    void testDoGetHttp_param_onFailure() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        HttpCallback<Response> callback = new HttpCallback<Response>() {
+            @Override
+            public void onSuccess(Response response) {
+                fail("Should not succeed");
+            }
+
+            @Override
+            public void onFailure(Throwable t) {
+                assertNotNull(t);
+                latch.countDown();
+            }
+
+            @Override
+            public void onCancelled() {
+                fail("Should not be cancelled");
+            }
+        };
+
+        Map<String, String> params = new HashMap<>();
+        params.put("key", "value");
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", "application/json");
+
+        HttpClientUtil.doGetWithHttp2("http://localhost:9999/invalid";, 
headers, callback, 30000);
+        assertTrue(latch.await(10, TimeUnit.SECONDS));
+    }
+
+    @Test
+    void testShutdownHookExecution() throws Exception {
+        String javaVersion = System.getProperty("java.version");
+        Assumptions.assumeTrue(
+                javaVersion.startsWith("1.8"), () -> "Skipping test: only runs 
on Java 8, current=" + javaVersion);
+        Class.forName("org.apache.seata.common.util.HttpClientUtil");
+
+        Class<?> clazz = Class.forName("java.lang.ApplicationShutdownHooks");
+        Field hooksField = clazz.getDeclaredField("hooks");
+        hooksField.setAccessible(true);
+        Map<Thread, Thread> hooks = (Map<Thread, Thread>) hooksField.get(null);
+        Thread targetHook = hooks.keySet().stream()
+                .filter(h -> {
+                    try {
+                        Field targetField = 
Thread.class.getDeclaredField("target");
+                        targetField.setAccessible(true);
+                        Object target = targetField.get(h);
+                        return target != null && 
target.toString().contains("HttpClientUtil");
+                    } catch (Exception e) {
+                        return false;
+                    }
+                })
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("No HttpClientUtil 
shutdown hook found"));
+
+        Field httpClientMapField = 
HttpClientUtil.class.getDeclaredField("HTTP_CLIENT_MAP");
+        httpClientMapField.setAccessible(true);
+        Map<Integer, Object> httpClientMap = (Map<Integer, Object>) 
httpClientMapField.get(null);
+
+        Field http2ClientMapField = 
HttpClientUtil.class.getDeclaredField("HTTP2_CLIENT_MAP");
+        http2ClientMapField.setAccessible(true);
+        Map<Integer, OkHttpClient> http2ClientMap = (Map<Integer, 
OkHttpClient>) http2ClientMapField.get(null);
+
+        CloseableHttpClient mockCloseableClient = 
mock(CloseableHttpClient.class);
+        OkHttpClient mockHttp2Client = mock(OkHttpClient.class, 
RETURNS_DEEP_STUBS);
+
+        httpClientMap.put(1, mockCloseableClient);
+        http2ClientMap.put(2, mockHttp2Client);
+
+        targetHook.run();
+
+        verify(mockCloseableClient, atLeastOnce()).close();
+        verify(mockHttp2Client.dispatcher().executorService(), 
atLeastOnce()).shutdown();
+        verify(mockHttp2Client.connectionPool(), atLeastOnce()).evictAll();
+    }
 }
diff --git 
a/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosConfigurationTest.java
 
b/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosConfigurationTest.java
index fd8e0c9831..0c30f58c0a 100644
--- 
a/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosConfigurationTest.java
+++ 
b/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosConfigurationTest.java
@@ -26,7 +26,6 @@ import org.apache.seata.config.ConfigurationChangeListener;
 import org.apache.seata.config.ConfigurationFactory;
 import org.apache.seata.config.Dispose;
 import org.apache.seata.config.processor.ConfigProcessor;
-import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
@@ -229,7 +228,6 @@ public class NacosConfigurationTest {
         Assertions.assertFalse(listener.invoked);
     }
 
-    @NotNull
     private static NacosConfiguration.NacosListener getNacosListener(String 
dataId, TestListener listener)
             throws ClassNotFoundException, NoSuchMethodException, 
InstantiationException, IllegalAccessException,
                     InvocationTargetException {
diff --git a/discovery/seata-discovery-namingserver/pom.xml 
b/discovery/seata-discovery-namingserver/pom.xml
index 69bfa9ba62..5e10ae2654 100644
--- a/discovery/seata-discovery-namingserver/pom.xml
+++ b/discovery/seata-discovery-namingserver/pom.xml
@@ -75,5 +75,9 @@
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/discovery/seata-discovery-raft/pom.xml 
b/discovery/seata-discovery-raft/pom.xml
index 643f792e06..a4f3a7a209 100644
--- a/discovery/seata-discovery-raft/pom.xml
+++ b/discovery/seata-discovery-raft/pom.xml
@@ -39,6 +39,10 @@
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
diff --git a/namingserver/pom.xml b/namingserver/pom.xml
index 756b7c8294..77c8bb26e3 100644
--- a/namingserver/pom.xml
+++ b/namingserver/pom.xml
@@ -180,6 +180,10 @@
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpasyncclient</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/initializer/db/SqlServerResourceIdInitializer.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/initializer/db/SqlServerResourceIdInitializer.java
index ecf50093af..375e7d0143 100644
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/initializer/db/SqlServerResourceIdInitializer.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/initializer/db/SqlServerResourceIdInitializer.java
@@ -20,7 +20,6 @@ package org.apache.seata.rm.datasource.initializer.db;
 import org.apache.seata.rm.datasource.DataSourceProxy;
 import 
org.apache.seata.rm.datasource.initializer.AbstractResourceIdInitializer;
 import org.apache.seata.sqlparser.util.JdbcConstants;
-import org.jetbrains.annotations.NotNull;
 
 public class SqlServerResourceIdInitializer extends 
AbstractResourceIdInitializer {
     @Override
@@ -52,7 +51,6 @@ public class SqlServerResourceIdInitializer extends 
AbstractResourceIdInitialize
         proxy.setResourceId(resourceId);
     }
 
-    @NotNull
     private static StringBuilder getParamsBuilder(String resourceId) {
         StringBuilder paramsBuilder = new StringBuilder();
         String paramUrl = resourceId.substring(resourceId.indexOf(';') + 1);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to