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]