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 d456cea081 feature : Upgrade HTTP client in common module to support 
HTTP/2 (#7492)
d456cea081 is described below

commit d456cea08128f0457d6dc920d1cb60e4e43bf59d
Author: Yongjun Hong <yongj...@apache.org>
AuthorDate: Wed Jul 30 23:37:11 2025 +0900

    feature : Upgrade HTTP client in common module to support HTTP/2 (#7492)
---
 changes/en-us/2.x.md                               |   2 +
 changes/zh-cn/2.x.md                               |   2 +
 common/pom.xml                                     |   4 +
 .../apache/seata/common/executor/HttpCallback.java |  44 ++++
 .../apache/seata/common/util/Http5ClientUtil.java  | 155 +++++++++++++
 .../apache/seata/common/util/HttpClientUtil.java   |   1 +
 .../seata/common/util/Http5ClientUtilTest.java     | 249 +++++++++++++++++++++
 7 files changed, 457 insertions(+)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index cd3a82d3ef..b53587bf1c 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:
 
 - [[#7485](https://github.com/apache/incubator-seata/pull/7485)] Add http 
request filter for seata-server
+- [[#7492](https://github.com/apache/incubator-seata/pull/7492)] upgrade HTTP 
client in common module to support HTTP/2
 
 
 ### bugfix:
@@ -63,6 +64,7 @@ Thanks to these contributors for their code commits. Please 
report an unintended
 - [slievrly](https://github.com/slievrly)
 - [YvCeung](https://github.com/YvCeung)
 - [xjlgod](https://github.com/xjlgod)
+- [YongGoose](https://github.com/YongGoose)
 
 
 Also, we receive many valuable issues, questions and advices from our 
community. Thanks for you all.
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index e84b0a1148..697dacb4de 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -21,6 +21,7 @@
 ### feature:
 
 - [[#7485](https://github.com/apache/incubator-seata/pull/7485)] 
给seata-server端的http请求添加过滤器
+- [[#7492](https://github.com/apache/incubator-seata/pull/7492)] 升级 common 
模块中的 HTTP 客户端以支持 HTTP/2
 
 
 ### bugfix:
@@ -62,6 +63,7 @@
 - [slievrly](https://github.com/slievrly)
 - [YvCeung](https://github.com/YvCeung)
 - [xjlgod](https://github.com/xjlgod)
+- [YongGoose](https://github.com/YongGoose)
 
 
 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。
diff --git a/common/pom.xml b/common/pom.xml
index 517aae769a..0db3696a5d 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -52,5 +52,9 @@
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/common/src/main/java/org/apache/seata/common/executor/HttpCallback.java 
b/common/src/main/java/org/apache/seata/common/executor/HttpCallback.java
new file mode 100644
index 0000000000..9f53241ac7
--- /dev/null
+++ b/common/src/main/java/org/apache/seata/common/executor/HttpCallback.java
@@ -0,0 +1,44 @@
+/*
+ * 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.executor;
+
+/**
+ * The interface HttpCallback.
+ *
+ * @param <T> the type parameter
+ */
+public interface HttpCallback<T> {
+
+    /**
+     * Called when the HTTP request is successful.
+     *
+     * @param result the result of the HTTP request
+     */
+    void onSuccess(T result);
+
+    /**
+     * Called when the HTTP request fails.
+     *
+     * @param e the exception that occurred during the HTTP request
+     */
+    void onFailure(Throwable e);
+
+    /**
+     * Called when the HTTP request is cancelled.
+     */
+    void onCancelled();
+}
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
new file mode 100644
index 0000000000..bcf95a00e5
--- /dev/null
+++ b/common/src/main/java/org/apache/seata/common/util/Http5ClientUtil.java
@@ -0,0 +1,155 @@
+/*
+ * 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 0490ad80f4..31a5f03ae3 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
@@ -51,6 +51,7 @@ public class HttpClientUtil {
 
     private static final PoolingHttpClientConnectionManager 
POOLING_HTTP_CLIENT_CONNECTION_MANAGER =
             new PoolingHttpClientConnectionManager();
+
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
     static {
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
new file mode 100644
index 0000000000..058ea439e0
--- /dev/null
+++ b/common/src/test/java/org/apache/seata/common/util/Http5ClientUtilTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.*;
+
+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));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@seata.apache.org
For additional commands, e-mail: notifications-h...@seata.apache.org

Reply via email to