diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 7705bc0bfa9..f1849b09571 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -94,6 +94,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#7615](https://github.com/seata/seata/pull/7615)] Refactor DataSourceProxy - [[#7617](https://github.com/seata/seata/pull/7617)] Refactor Alibaba Dubbo and HSF +- [[#7684](https://github.com/apache/incubator-seata/pull/7684)] Refactor and unify the apis of http1 and http2 ### doc: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index a34a0e3b19e..e5927016b23 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -92,6 +92,7 @@ - [[#7615](https://github.com/seata/seata/pull/7615)] 重构 DataSourceProxy - [[#7617](https://github.com/seata/seata/pull/7617)] 重构 Alibaba Dubbo 和 HSF 模块 +- [[#7684](https://github.com/apache/incubator-seata/pull/7684)] 重构并统一http1和http2的API ### doc: diff --git a/common/pom.xml b/common/pom.xml index 2d2d047e8ec..8a1cfffea8a 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -55,6 +55,7 @@ com.squareup.okhttp3 okhttp + provided diff --git a/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java b/common/src/main/java/org/apache/seata/common/http/Http1HttpExecutor.java similarity index 63% rename from common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java rename to common/src/main/java/org/apache/seata/common/http/Http1HttpExecutor.java index 31a5f03ae38..e986230aa2e 100644 --- a/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java +++ b/common/src/main/java/org/apache/seata/common/http/Http1HttpExecutor.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seata.common.util; +package org.apache.seata.common.http; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.NameValuePair; @@ -31,6 +31,9 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,9 +46,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class HttpClientUtil { +@LoadLevel(name = "Http1", order = 1) +public class Http1HttpExecutor implements HttpExecutor { - private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Http1HttpExecutor.class); private static final Map HTTP_CLIENT_MAP = new ConcurrentHashMap<>(); @@ -54,6 +58,25 @@ public class HttpClientUtil { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static volatile Http1HttpExecutor instance; + + /** + * Gets instance. + * + * @return the instance + */ + public static Http1HttpExecutor getInstance() { + + if (instance == null) { + synchronized (Http1HttpExecutor.class) { + if (instance == null) { + instance = new Http1HttpExecutor(); + } + } + } + return instance; + } + static { POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(10); POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(10); @@ -69,9 +92,9 @@ public class HttpClientUtil { }))); } - // post request - public static CloseableHttpResponse doPost( - String url, Map params, Map header, int timeout) throws IOException { + @Override + public HttpResult doPost(String url, Map params, Map header, int timeout) + throws IOException { try { URIBuilder builder = new URIBuilder(url); URI uri = builder.build(); @@ -96,24 +119,27 @@ public static CloseableHttpResponse doPost( httpPost.setEntity(stringEntity); } } - CloseableHttpClient client = HTTP_CLIENT_MAP.computeIfAbsent(timeout, k -> HttpClients.custom() - .setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER) - .setDefaultRequestConfig(RequestConfig.custom() - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout) - .setConnectTimeout(timeout) - .build()) - .build()); - return client.execute(httpPost); + CloseableHttpClient client = getHttpClient(timeout); + try (CloseableHttpResponse response = client.execute(httpPost)) { + int statusCode = response.getStatusLine() != null + ? response.getStatusLine().getStatusCode() + : 0; + + String responseBody = null; + if (response.getEntity() != null) { + responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + } + + return new HttpResult(statusCode, responseBody, null); + } } catch (URISyntaxException | ClientProtocolException e) { LOGGER.error(e.getMessage(), e); } return null; } - // post request - public static CloseableHttpResponse doPost(String url, String body, Map header, int timeout) - throws IOException { + @Override + public HttpResult doPost(String url, String body, Map header, int timeout) throws IOException { try { URIBuilder builder = new URIBuilder(url); URI uri = builder.build(); @@ -129,24 +155,28 @@ public static CloseableHttpResponse doPost(String url, String body, Map HttpClients.custom() - .setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER) - .setDefaultRequestConfig(RequestConfig.custom() - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout) - .setConnectTimeout(timeout) - .build()) - .build()); - return client.execute(httpPost); + CloseableHttpClient client = getHttpClient(timeout); + try (CloseableHttpResponse response = client.execute(httpPost)) { + int statusCode = response.getStatusLine() != null + ? response.getStatusLine().getStatusCode() + : 0; + + String responseBody = null; + if (response.getEntity() != null) { + responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + } + + return new HttpResult(statusCode, responseBody, null); + } } catch (URISyntaxException | ClientProtocolException e) { LOGGER.error(e.getMessage(), e); } return null; } - // get request - public static CloseableHttpResponse doGet( - String url, Map param, Map header, int timeout) throws IOException { + @Override + public HttpResult doGet(String url, Map param, Map header, int timeout) + throws IOException { try { URIBuilder builder = new URIBuilder(url); if (param != null) { @@ -159,40 +189,33 @@ public static CloseableHttpResponse doGet( if (header != null) { header.forEach(httpGet::addHeader); } - CloseableHttpClient client = HTTP_CLIENT_MAP.computeIfAbsent(timeout, k -> HttpClients.custom() - .setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER) - .setDefaultRequestConfig(RequestConfig.custom() - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout) - .setConnectTimeout(timeout) - .build()) - .build()); - return client.execute(httpGet); + CloseableHttpClient client = getHttpClient(timeout); + try (CloseableHttpResponse response = client.execute(httpGet)) { + int statusCode = response.getStatusLine() != null + ? response.getStatusLine().getStatusCode() + : 0; + + String responseBody = null; + if (response.getEntity() != null) { + responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + } + + return new HttpResult(statusCode, responseBody, null); + } } catch (URISyntaxException | ClientProtocolException e) { LOGGER.error(e.getMessage(), e); } return null; } - public static CloseableHttpResponse doPostJson( - String url, String jsonBody, Map headers, int timeout) throws IOException { - RequestConfig requestConfig = RequestConfig.custom() - .setSocketTimeout(timeout) - .setConnectTimeout(timeout) - .build(); - - HttpPost post = new HttpPost(url); - post.setConfig(requestConfig); - - if (headers != null) { - headers.forEach(post::addHeader); - } - post.setHeader("Content-Type", "application/json"); - - StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8); - post.setEntity(entity); - - CloseableHttpClient client = HttpClients.createDefault(); - return client.execute(post); + private static CloseableHttpClient getHttpClient(int timeout) { + return HTTP_CLIENT_MAP.computeIfAbsent(timeout, k -> HttpClients.custom() + .setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout) + .setConnectTimeout(timeout) + .build()) + .build()); } } diff --git a/common/src/main/java/org/apache/seata/common/http/Http2HttpExecutor.java b/common/src/main/java/org/apache/seata/common/http/Http2HttpExecutor.java new file mode 100644 index 00000000000..b873fccfed3 --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/http/Http2HttpExecutor.java @@ -0,0 +1,245 @@ +/* + * 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.http; + +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.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.apache.seata.common.loader.LoadLevel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@LoadLevel(name = "Http2", order = 2) +public class Http2HttpExecutor implements HttpExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(Http2HttpExecutor.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 volatile Http2HttpExecutor instance; + + /** + * Gets instance. + * + * @return the instance + */ + public static Http2HttpExecutor getInstance() { + + if (instance == null) { + synchronized (Http2HttpExecutor.class) { + if (instance == null) { + instance = new Http2HttpExecutor(); + } + } + } + return instance; + } + + 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"); + + @Override + public HttpResult doPost(String url, Map params, Map headers, int timeout) + throws IOException { + 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(); + + CompletableFuture future = new CompletableFuture<>(); + HTTP_CLIENT.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) { + try { + String responseBody = + response.body() != null ? response.body().string() : null; + future.complete(new HttpResult(response.code(), responseBody, response)); + } catch (IOException e) { + future.completeExceptionally(e); + } finally { + response.close(); + } + } + }); + + try { + return future.get(timeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new IOException("HTTP2 request failed", e); + } + + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + throw new IOException("Failed to serialize request params to JSON", e); + } + } + + @Override + public HttpResult doPost(String url, String body, Map headers, int timeout) throws IOException { + try { + 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(); + + CompletableFuture future = new CompletableFuture<>(); + HTTP_CLIENT.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) { + try { + String responseBody = + response.body() != null ? response.body().string() : null; + future.complete(new HttpResult(response.code(), responseBody, response)); + } catch (IOException e) { + future.completeExceptionally(e); + } finally { + response.close(); + } + } + }); + + try { + return future.get(timeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new IOException("HTTP2 POST request failed or timed out", e); + } + } catch (Exception e) { + throw new IOException("Failed to execute HTTP2 POST request", e); + } + } + + @Override + public HttpResult doGet(String url, Map param, Map headers, int timeout) + throws IOException { + try { + HttpUrl finalUrl = Objects.requireNonNull(HttpUrl.get(url), "Invalid URL"); + if (param != null) { + for (Map.Entry entry : param.entrySet()) { + finalUrl = finalUrl.newBuilder() + .addQueryParameter(entry.getKey(), entry.getValue()) + .build(); + } + } + + Headers.Builder headerBuilder = new Headers.Builder(); + if (headers != null) { + headers.forEach(headerBuilder::add); + } + + Request request = new Request.Builder() + .url(finalUrl) + .headers(headerBuilder.build()) + .get() + .build(); + + CompletableFuture future = new CompletableFuture<>(); + HTTP_CLIENT.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) { + try { + String responseBody = + response.body() != null ? response.body().string() : null; + future.complete(new HttpResult(response.code(), responseBody, response)); + } catch (IOException e) { + future.completeExceptionally(e); + } finally { + response.close(); + } + } + }); + + try { + return future.get(timeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new IOException("HTTP2 GET request failed or timed out", e); + } + + } catch (Exception e) { + throw new IOException("Failed to execute HTTP2 GET request", e); + } + } + + private RequestBody createRequestBody(Map 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); + } + } +} diff --git a/common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java b/common/src/main/java/org/apache/seata/common/http/HttpExecutor.java similarity index 64% rename from common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java rename to common/src/main/java/org/apache/seata/common/http/HttpExecutor.java index b20f1cbfcec..340811736c5 100644 --- a/common/src/test/java/org/apache/seata/common/util/HttpClientUtilTest.java +++ b/common/src/main/java/org/apache/seata/common/http/HttpExecutor.java @@ -14,19 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.seata.common.util; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +package org.apache.seata.common.http; import java.io.IOException; -import java.util.HashMap; +import java.util.Map; + +public interface HttpExecutor { + + HttpResult doPost(String url, Map params, Map header, int timeout) + throws IOException; -public class HttpClientUtilTest { + HttpResult doPost(String url, String body, Map header, int timeout) throws IOException; - @Test - public void testDoPost() throws IOException { - Assertions.assertNull(HttpClientUtil.doPost("test", new HashMap<>(), new HashMap<>(), 0)); - Assertions.assertNull(HttpClientUtil.doGet("test", new HashMap<>(), new HashMap<>(), 0)); - } + HttpResult doGet(String url, Map param, Map header, int timeout) throws IOException; } diff --git a/common/src/main/java/org/apache/seata/common/http/HttpExecutorFactory.java b/common/src/main/java/org/apache/seata/common/http/HttpExecutorFactory.java new file mode 100644 index 00000000000..c89ccd2026a --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/http/HttpExecutorFactory.java @@ -0,0 +1,51 @@ +/* + * 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.http; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpExecutorFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpExecutorFactory.class); + + private static final String HTTP1_IMPL = "Http1"; + private static final String HTTP2_IMPL = "Http2"; + private static final HttpExecutor INSTANCE = createInstance(); + + private HttpExecutorFactory() {} + + private static HttpExecutor createInstance() { + String implName = isOkHttpAvailable() ? HTTP2_IMPL : HTTP1_IMPL; + return EnhancedServiceLoader.load(HttpExecutor.class, implName); + } + + private static boolean isOkHttpAvailable() { + try { + Class.forName("okhttp3.OkHttpClient", false, HttpExecutorFactory.class.getClassLoader()); + return true; + } catch (ClassNotFoundException ignored) { + LOGGER.warn("HTTP2 implementation not available in classpath, falling back to default executor!"); + return false; + } + } + + public static HttpExecutor getInstance() { + return INSTANCE; + } +} diff --git a/common/src/main/java/org/apache/seata/common/http/HttpResult.java b/common/src/main/java/org/apache/seata/common/http/HttpResult.java new file mode 100644 index 00000000000..7fd1c82922d --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/http/HttpResult.java @@ -0,0 +1,56 @@ +/* + * 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.http; + +public class HttpResult { + + private int statusCode; + private String responseBody; + private Object rawResponse; + + public HttpResult() {} + + public HttpResult(int statusCode, String responseBody, Object rawResponse) { + this.statusCode = statusCode; + this.responseBody = responseBody; + this.rawResponse = rawResponse; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public String getResponseBody() { + return responseBody; + } + + public void setResponseBody(String responseBody) { + this.responseBody = responseBody; + } + + public Object getRawResponse() { + return rawResponse; + } + + public void setRawResponse(Object rawResponse) { + this.rawResponse = rawResponse; + } +} 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 bcf95a00e55..00000000000 --- 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 params, Map headers, HttpCallback 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 headers, HttpCallback 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 headers, final HttpCallback 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 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 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/resources/META-INF/services/org.apache.seata.common.http.HttpExecutor b/common/src/main/resources/META-INF/services/org.apache.seata.common.http.HttpExecutor new file mode 100644 index 00000000000..499065af264 --- /dev/null +++ b/common/src/main/resources/META-INF/services/org.apache.seata.common.http.HttpExecutor @@ -0,0 +1,18 @@ +# +# 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. +# +org.apache.seata.common.http.Http1HttpExecutor +org.apache.seata.common.http.Http2HttpExecutor diff --git a/common/src/test/java/org/apache/seata/common/http/HttpExecutorFactoryTest.java b/common/src/test/java/org/apache/seata/common/http/HttpExecutorFactoryTest.java new file mode 100644 index 00000000000..a488cb4dce1 --- /dev/null +++ b/common/src/test/java/org/apache/seata/common/http/HttpExecutorFactoryTest.java @@ -0,0 +1,43 @@ +/* + * 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.http; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +class HttpExecutorFactoryTest { + @Test + void testGetInstance_singleton() { + HttpExecutor mockExecutor = mock(HttpExecutor.class); + + try (MockedStatic loaderMock = mockStatic(EnhancedServiceLoader.class)) { + loaderMock + .when(() -> EnhancedServiceLoader.load(HttpExecutor.class, "Http1")) + .thenReturn(mockExecutor); + + HttpExecutor first = HttpExecutorFactory.getInstance(); + HttpExecutor second = HttpExecutorFactory.getInstance(); + + assertEquals(first, second); + } + } +} diff --git a/common/src/test/java/org/apache/seata/common/util/Http1HttpExecutorTest.java b/common/src/test/java/org/apache/seata/common/util/Http1HttpExecutorTest.java new file mode 100644 index 00000000000..3695f68fa2d --- /dev/null +++ b/common/src/test/java/org/apache/seata/common/util/Http1HttpExecutorTest.java @@ -0,0 +1,61 @@ +/* + * 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 org.apache.seata.common.http.Http1HttpExecutor; +import org.apache.seata.common.http.HttpResult; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Http1HttpExecutorTest { + + @Test + public void testDoPost() throws IOException { + Assertions.assertNull(Http1HttpExecutor.getInstance().doPost("test", new HashMap<>(), new HashMap<>(), 0)); + Assertions.assertNull(Http1HttpExecutor.getInstance().doGet("test", new HashMap<>(), new HashMap<>(), 0)); + } + + @Test + void testDoGetBaidu() throws Exception { + HashMap param = new HashMap<>(); + param.put("wd", "seata"); + HttpResult httpResult = Http1HttpExecutor.getInstance().doGet("https://www.baidu.com", param, null, 5000); + + assertNotNull(httpResult); + assertEquals(200, httpResult.getStatusCode()); + } + + @Test + void testDoPostNormal() throws Exception { + HashMap header = new HashMap<>(); + header.put("Content-Type", "application/json"); + + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("https://postman-echo.com/post", "{\"name\":\"seata\"}", header, 5000); + + assertNotNull(httpResult); + assertEquals(200, httpResult.getStatusCode()); + assertTrue(httpResult.getResponseBody().contains("seata")); + } +} diff --git a/common/src/test/java/org/apache/seata/common/util/Http2HttpExecutorTest.java b/common/src/test/java/org/apache/seata/common/util/Http2HttpExecutorTest.java new file mode 100644 index 00000000000..20264707118 --- /dev/null +++ b/common/src/test/java/org/apache/seata/common/util/Http2HttpExecutorTest.java @@ -0,0 +1,128 @@ +/* + * 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.http.Http2HttpExecutor; +import org.apache.seata.common.http.HttpResult; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class Http2HttpExecutorTest { + + @Test + void testDoPostHttp_param_onSuccess() throws Exception { + + Map params = new HashMap<>(); + params.put("key", "value"); + + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + HttpResult responseHttpResult = + Http2HttpExecutor.getInstance().doPost("https://www.cloudflare.com/", params, headers, 10000); + + assertNotNull(responseHttpResult); + assertEquals(Protocol.HTTP_2, ((Response) responseHttpResult.getRawResponse()).protocol()); + } + + @Test + void testDoPostHttp_param_onFailure() { + Map params = new HashMap<>(); + params.put("key", "value"); + + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + assertThrows(Exception.class, () -> Http2HttpExecutor.getInstance() + .doPost("http://localhost:9999/invalid", params, headers, 3000)); + } + + @Test + void testDoPostHttp_body_onSuccess() throws Exception { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + HttpResult result = Http2HttpExecutor.getInstance() + .doPost("https://www.cloudflare.com/", "{\"key\":\"value\"}", headers, 10000); + assertNotNull(result); + assertEquals(Protocol.HTTP_2, ((Response) result.getRawResponse()).protocol()); + } + + @Test + void testDoPostHttp_body_onFailure() { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + assertThrows(Exception.class, () -> Http2HttpExecutor.getInstance() + .doPost("http://localhost:9999/invalid", "{\"key\":\"value\"}", headers, 3000)); + } + + @Test + void testDoPostHttp_param_onSuccess_forceHttp1() throws Exception { + Map params = new HashMap<>(); + params.put("key", "value"); + + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + HttpResult result = Http2HttpExecutor.getInstance().doPost("http://httpbin.org/post", params, headers, 10000); + assertNotNull(result); + assertEquals(Protocol.HTTP_1_1, ((Response) result.getRawResponse()).protocol()); + } + + @Test + void testDoGetHttp_onSuccess() throws Exception { + Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + + HttpResult result = Http2HttpExecutor.getInstance().doGet("https://www.cloudflare.com/", null, headers, 10000); + assertNotNull(result); + assertEquals(Protocol.HTTP_2, ((Response) result.getRawResponse()).protocol()); + } + + @Test + void testDoGetHttp_withParams_onSuccess() throws Exception { + Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + + Map params = new HashMap<>(); + params.put("key", "value"); + + HttpResult result = Http2HttpExecutor.getInstance().doGet("https://www.cloudflare.com", params, headers, 10000); + assertNotNull(result); + assertEquals(Protocol.HTTP_2, ((Response) result.getRawResponse()).protocol()); + } + + @Test + void testDoPostHttp_body_onSuccess_forceHttp1() throws Exception { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + HttpResult result = Http2HttpExecutor.getInstance() + .doPost("http://httpbin.org/post", "{\"key\":\"value\"}", headers, 10000); + assertNotNull(result); + assertEquals(Protocol.HTTP_1_1, ((Response) result.getRawResponse()).protocol()); + } +} 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 9b550067e75..00000000000 --- 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 callback = new HttpCallback() { - @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 params = new HashMap<>(); - params.put("key", "value"); - - Map 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 callback = new HttpCallback() { - @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 params = new HashMap<>(); - params.put("key", "value"); - - Map 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 callback = new HttpCallback() { - @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 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 callback = new HttpCallback() { - @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 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 callback = new HttpCallback() { - @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 params = new HashMap<>(); - params.put("key", "value"); - - Map 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 callback = new HttpCallback() { - @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 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 callback = new HttpCallback() { - @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 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/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 470cce840d8..5aecd455fa4 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 @@ -24,7 +24,6 @@ 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.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -195,7 +194,6 @@ public void testInnerReceiveThrowException() throws Exception { 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/src/main/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImpl.java b/discovery/seata-discovery-namingserver/src/main/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImpl.java index 8eb28c439fe..adfa32f065b 100644 --- a/discovery/seata-discovery-namingserver/src/main/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImpl.java +++ b/discovery/seata-discovery-namingserver/src/main/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImpl.java @@ -21,14 +21,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.ContentType; import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.exception.AuthenticationFailedException; import org.apache.seata.common.exception.RetryableException; +import org.apache.seata.common.http.Http1HttpExecutor; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.common.metadata.Cluster; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Instance; @@ -38,7 +37,6 @@ import org.apache.seata.common.metadata.namingserver.Unit; import org.apache.seata.common.thread.NamedThreadFactory; import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.HttpClientUtil; import org.apache.seata.common.util.NetUtil; import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.Configuration; @@ -49,7 +47,6 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Arrays; @@ -230,8 +227,9 @@ public void doRegister(Instance instance, List urlList) throws Retryable } header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); - try (CloseableHttpResponse response = HttpClientUtil.doPost(url, jsonBody, header, 3000)) { - int statusCode = response.getStatusLine().getStatusCode(); + try { + HttpResult httpResult = Http1HttpExecutor.getInstance().doPost(url, jsonBody, header, 3000); + int statusCode = httpResult.getStatusCode(); if (statusCode == 200) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("instance has been registered successfully:{}", statusCode); @@ -249,8 +247,9 @@ public boolean doHealthCheck(String url) { url = HTTP_PREFIX + url + "/naming/v1/health"; Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); - try (CloseableHttpResponse response = HttpClientUtil.doGet(url, null, header, 3000)) { - int statusCode = response.getStatusLine().getStatusCode(); + try { + HttpResult httpResult = Http1HttpExecutor.getInstance().doGet(url, null, header, 3000); + int statusCode = httpResult.getStatusCode(); return statusCode == 200; } catch (Exception e) { return false; @@ -274,8 +273,9 @@ public void unregister(Instance instance) { url += params; Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); - try (CloseableHttpResponse response = HttpClientUtil.doPost(url, jsonBody, header, 3000)) { - int statusCode = response.getStatusLine().getStatusCode(); + try { + HttpResult httpResult = Http1HttpExecutor.getInstance().doPost(url, jsonBody, header, 3000); + int statusCode = httpResult.getStatusCode(); if (statusCode == 200) { LOGGER.info("instance has been unregistered successfully:{}", statusCode); } else { @@ -357,10 +357,11 @@ public boolean watch(String vGroup) throws RetryableException { if (StringUtils.isNotBlank(jwtToken)) { header.put(AUTHORIZATION_HEADER, jwtToken); } - try (CloseableHttpResponse response = HttpClientUtil.doPost(watchAddr, (String) null, header, 30000)) { - if (response != null) { - StatusLine statusLine = response.getStatusLine(); - return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + try { + HttpResult httpResult = Http1HttpExecutor.getInstance().doPost(watchAddr, (String) null, header, 30000); + + if (httpResult != null) { + return httpResult.getStatusCode() == HttpStatus.SC_OK; } } catch (Exception e) { LOGGER.error("watch failed: {}", e.getMessage()); @@ -438,12 +439,14 @@ public List refreshGroup(String vGroup) throws IOException, R if (StringUtils.isNotBlank(jwtToken)) { header.put(AUTHORIZATION_HEADER, jwtToken); } - try (CloseableHttpResponse response = HttpClientUtil.doGet(url, paraMap, header, 3000)) { - if (response == null || response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + try { + HttpResult httpResult = Http1HttpExecutor.getInstance().doGet(url, paraMap, header, 3000); + if (httpResult == null || httpResult.getStatusCode() != HttpStatus.SC_OK) { + assert httpResult != null; throw new NamingRegistryException("cannot lookup server list in vgroup: " + vGroup + ", http code: " - + response.getStatusLine().getStatusCode()); + + httpResult.getStatusCode()); } - String jsonResponse = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + String jsonResponse = httpResult.getResponseBody(); // jsonResponse -> MetaResponse MetaResponse metaResponse = OBJECT_MAPPER.readValue(jsonResponse, new TypeReference() {}); return handleMetadata(metaResponse, vGroup); @@ -576,11 +579,12 @@ private static void refreshToken(String namingServerAddress) throws RetryableExc Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); String response = null; - try (CloseableHttpResponse httpResponse = - HttpClientUtil.doPost("http://" + namingServerAddress + "/api/v1/auth/login", param, header, 1000)) { - if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + try { + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://" + namingServerAddress + "/api/v1/auth/login", param, header, 1000); + if (httpResult != null) { + if (httpResult.getStatusCode() == HttpStatus.SC_OK) { + response = httpResult.getResponseBody(); JsonNode jsonNode = OBJECT_MAPPER.readTree(response); String codeStatus = jsonNode.get("code").asText(); if (!StringUtils.equals(codeStatus, "200")) { diff --git a/discovery/seata-discovery-namingserver/src/test/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImplTest.java b/discovery/seata-discovery-namingserver/src/test/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImplTest.java index 4f72e1f5b19..5f49aa2c204 100644 --- a/discovery/seata-discovery-namingserver/src/test/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImplTest.java +++ b/discovery/seata-discovery-namingserver/src/test/java/org/apache/seata/discovery/registry/namingserver/NamingserverRegistryServiceImplTest.java @@ -16,18 +16,17 @@ */ package org.apache.seata.discovery.registry.namingserver; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.ContentType; import org.apache.http.protocol.HTTP; import org.apache.seata.common.holder.ObjectHolder; +import org.apache.seata.common.http.Http1HttpExecutor; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.common.metadata.Cluster; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.metadata.namingserver.MetaResponse; import org.apache.seata.common.metadata.namingserver.NamingServerNode; import org.apache.seata.common.metadata.namingserver.Unit; -import org.apache.seata.common.util.HttpClientUtil; import org.apache.seata.config.Configuration; import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.discovery.registry.RegistryService; @@ -35,7 +34,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; @@ -56,13 +54,6 @@ import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; class NamingserverRegistryServiceImplTest { @@ -104,22 +95,6 @@ public void unregister1() throws Exception { namingserverRegistryService.unregister(inetSocketAddress); } - @Test - public void testWatchCoversRefreshToken() throws Exception { - - NamingserverRegistryServiceImpl spyService = Mockito.spy(NamingserverRegistryServiceImpl.getInstance()); - doReturn("127.0.0.1:8081").when(spyService).getNamingAddr(); - - CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class); - StatusLine mockStatusLine = mock(StatusLine.class); - when(mockStatusLine.getStatusCode()).thenReturn(200); - when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); - mockStatic(HttpClientUtil.class); - when(HttpClientUtil.doPost(anyString(), anyString(), anyMap(), anyInt())) - .thenReturn(mockResponse); - spyService.watch("testGroup"); - } - @Test @Disabled public void getNamingAddrsTest() { @@ -389,7 +364,7 @@ public void createGroupInCluster(String namespace, String vGroup, String cluster Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); try { - CloseableHttpResponse response = HttpClientUtil.doGet(url, paraMap, header, 30000); + HttpResult httpResult = Http1HttpExecutor.getInstance().doGet(url, paraMap, header, 30000); } catch (Exception e) { throw new RemoteException(); } diff --git a/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java b/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java index ded156e6c62..37841d2d6d7 100644 --- a/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java +++ b/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java @@ -20,22 +20,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.ContentType; import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.exception.AuthenticationFailedException; import org.apache.seata.common.exception.NotSupportYetException; import org.apache.seata.common.exception.ParseEndpointException; import org.apache.seata.common.exception.RetryableException; +import org.apache.seata.common.http.Http1HttpExecutor; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.common.metadata.Metadata; import org.apache.seata.common.metadata.MetadataResponse; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.thread.NamedThreadFactory; import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.HttpClientUtil; import org.apache.seata.common.util.NetUtil; import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.ConfigChangeListener; @@ -47,7 +45,6 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -67,7 +64,6 @@ /** * The type File registry service. - * */ public class RaftRegistryServiceImpl implements RegistryService { @@ -425,11 +421,11 @@ private static boolean watch() throws RetryableException { if (StringUtils.isNotBlank(jwtToken)) { header.put(AUTHORIZATION_HEADER, jwtToken); } - try (CloseableHttpResponse response = - HttpClientUtil.doPost("http://" + tcAddress + "/metadata/v1/watch", param, header, 30000)) { - if (response != null) { - StatusLine statusLine = response.getStatusLine(); - if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + try { + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://" + tcAddress + "/metadata/v1/watch", param, header, 30000); + if (httpResult != null) { + if (httpResult.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { throw new RetryableException("Authentication failed!"); } else { @@ -437,7 +433,7 @@ private static boolean watch() throws RetryableException { "Authentication failed! you should configure the correct username and password."); } } - return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + return httpResult.getStatusCode() == HttpStatus.SC_OK; } } catch (IOException e) { LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage()); @@ -498,12 +494,13 @@ private static void acquireClusterMetaData(String clusterName, String group) thr Map param = new HashMap<>(); param.put("group", group); String response = null; - try (CloseableHttpResponse httpResponse = - HttpClientUtil.doGet("http://" + tcAddress + "/metadata/v1/cluster", param, header, 1000)) { - if (httpResponse != null) { - int statusCode = httpResponse.getStatusLine().getStatusCode(); + try { + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doGet("http://" + tcAddress + "/metadata/v1/cluster", param, header, 1000); + if (httpResult != null) { + int statusCode = httpResult.getStatusCode(); if (statusCode == HttpStatus.SC_OK) { - response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + response = httpResult.getResponseBody(); } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { refreshToken(tcAddress); @@ -544,11 +541,12 @@ private static void refreshToken(String tcAddress) throws RetryableException { Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); String response = null; - try (CloseableHttpResponse httpResponse = - HttpClientUtil.doPost("http://" + tcAddress + "/api/v1/auth/login", param, header, 1000)) { - if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + try { + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://" + tcAddress + "/api/v1/auth/login", param, header, 1000); + if (httpResult != null) { + if (httpResult.getStatusCode() == HttpStatus.SC_OK) { + response = httpResult.getResponseBody(); JsonNode jsonNode = OBJECT_MAPPER.readTree(response); String codeStatus = jsonNode.get("code").asText(); if (!StringUtils.equals(codeStatus, "200")) { diff --git a/discovery/seata-discovery-raft/src/test/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImplTest.java b/discovery/seata-discovery-raft/src/test/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImplTest.java index 90b9f76a0b8..a67e5bd7622 100644 --- a/discovery/seata-discovery-raft/src/test/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImplTest.java +++ b/discovery/seata-discovery-raft/src/test/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImplTest.java @@ -19,30 +19,33 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.StringEntity; +import org.apache.seata.common.exception.AuthenticationFailedException; +import org.apache.seata.common.http.Http1HttpExecutor; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.common.metadata.MetadataResponse; import org.apache.seata.common.metadata.Node; -import org.apache.seata.common.util.*; import org.apache.seata.config.ConfigurationFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.mockito.Mockito; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.List; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; class RaftRegistryServiceImplTest { @@ -67,29 +70,30 @@ public static void adAfterClass() throws Exception { * test whether throws exception when login failed */ @Test - public void testLoginFailed() throws IOException, NoSuchMethodException { - String jwtToken = "null"; - String responseBody = - "{\"code\":\"401\",\"message\":\"Login failed\",\"data\":\"" + jwtToken + "\",\"success\":false}"; - - try (MockedStatic mockedStatic = Mockito.mockStatic(HttpClientUtil.class)) { - - CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class); - StatusLine mockStatusLine = mock(StatusLine.class); - - when(mockResponse.getEntity()).thenReturn(new StringEntity(responseBody)); - when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); - when(mockStatusLine.getStatusCode()).thenReturn(HttpStatus.SC_OK); - - when(HttpClientUtil.doPost(any(String.class), any(Map.class), any(Map.class), any(int.class))) - .thenReturn(mockResponse); - - // Use reflection to access and invoke the private method - Method refreshTokenMethod = RaftRegistryServiceImpl.class.getDeclaredMethod("refreshToken", String.class); - refreshTokenMethod.setAccessible(true); - assertThrows( - Exception.class, - () -> refreshTokenMethod.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092")); + void testRefreshToken_loginFailed() throws Exception { + + String responseBody = "{\"code\":\"401\",\"message\":\"Login failed\",\"data\":null,\"success\":false}"; + HttpResult mockResult = new HttpResult(); + mockResult.setStatusCode(HttpStatus.SC_OK); + mockResult.setResponseBody(responseBody); + + Http1HttpExecutor mockExecutor = mock(Http1HttpExecutor.class); + when(mockExecutor.doPost(anyString(), anyMap(), anyMap(), anyInt())).thenReturn(mockResult); + + try (MockedStatic mockedStatic = mockStatic(Http1HttpExecutor.class)) { + mockedStatic.when(Http1HttpExecutor::getInstance).thenReturn(mockExecutor); + + Method method = RaftRegistryServiceImpl.class.getDeclaredMethod("refreshToken", String.class); + method.setAccessible(true); + + try { + method.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092"); + fail("Expected AuthenticationFailedException to be thrown"); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + assertNotNull(cause); + assertInstanceOf(AuthenticationFailedException.class, cause); + } } } @@ -104,26 +108,20 @@ public void testRefreshTokenSuccess() String responseBody = "{\"code\":\"200\",\"message\":\"success\",\"data\":\"" + jwtToken + "\",\"success\":true}"; - try (MockedStatic mockedStatic = Mockito.mockStatic(HttpClientUtil.class)) { - - CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class); - StatusLine mockStatusLine = mock(StatusLine.class); + HttpResult mockResult = new HttpResult(); + mockResult.setStatusCode(HttpStatus.SC_OK); + mockResult.setResponseBody(responseBody); - when(mockResponse.getEntity()).thenReturn(new StringEntity(responseBody)); - when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); - when(mockStatusLine.getStatusCode()).thenReturn(HttpStatus.SC_OK); + Http1HttpExecutor mockExecutor = mock(Http1HttpExecutor.class); + when(mockExecutor.doPost(anyString(), anyMap(), anyMap(), anyInt())).thenReturn(mockResult); - when(HttpClientUtil.doPost(any(String.class), any(Map.class), any(Map.class), any(int.class))) - .thenReturn(mockResponse); + try (MockedStatic mockedStatic = mockStatic(Http1HttpExecutor.class)) { + mockedStatic.when(Http1HttpExecutor::getInstance).thenReturn(mockExecutor); - Method refreshTokenMethod = RaftRegistryServiceImpl.class.getDeclaredMethod("refreshToken", String.class); - refreshTokenMethod.setAccessible(true); - refreshTokenMethod.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092"); - Field jwtTokenField = RaftRegistryServiceImpl.class.getDeclaredField("jwtToken"); - jwtTokenField.setAccessible(true); - String jwtTokenAct = (String) jwtTokenField.get(null); + Method method = RaftRegistryServiceImpl.class.getDeclaredMethod("refreshToken", String.class); + method.setAccessible(true); - assertEquals(jwtToken, jwtTokenAct); + method.invoke(RaftRegistryServiceImpl.getInstance(), "127.0.0.1:8092"); } } diff --git a/namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java b/namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java index 778a36be830..dc5fc58111a 100644 --- a/namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java +++ b/namingserver/src/main/java/org/apache/seata/namingserver/manager/NamingManager.java @@ -20,10 +20,11 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.RemovalCause; import com.github.benmanes.caffeine.cache.RemovalListener; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.ContentType; import org.apache.http.protocol.HTTP; import org.apache.seata.common.NamingServerConstants; +import org.apache.seata.common.http.HttpExecutorFactory; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.common.metadata.Cluster; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; @@ -31,7 +32,6 @@ import org.apache.seata.common.metadata.namingserver.Unit; import org.apache.seata.common.result.Result; import org.apache.seata.common.result.SingleResult; -import org.apache.seata.common.util.HttpClientUtil; import org.apache.seata.common.util.StringUtils; import org.apache.seata.namingserver.entity.bo.ClusterBO; import org.apache.seata.namingserver.entity.bo.NamespaceBO; @@ -176,11 +176,11 @@ public Result createGroup(String namespace, String vGroup, String cluste Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - try (CloseableHttpResponse closeableHttpResponse = HttpClientUtil.doGet(httpUrl, params, header, 3000)) { - if (closeableHttpResponse == null - || closeableHttpResponse.getStatusLine().getStatusCode() != 200) { + try { + HttpResult httpResult = HttpExecutorFactory.getInstance().doGet(httpUrl, params, header, 3000); + if (httpResult == null || httpResult.getStatusCode() != 200) { return new Result<>( - String.valueOf(closeableHttpResponse.getStatusLine().getStatusCode()), + String.valueOf(httpResult != null ? httpResult.getStatusCode() : 0), "add vGroup in new cluster failed"); } LOGGER.info( @@ -209,12 +209,12 @@ public Result removeGroup(Unit unit, String vGroup, String clusterName, params.put(NamingServerConstants.CONSTANT_UNIT, unitName); Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - try (CloseableHttpResponse closeableHttpResponse = HttpClientUtil.doGet(httpUrl, params, header, 3000)) { - if (closeableHttpResponse == null - || closeableHttpResponse.getStatusLine().getStatusCode() != 200) { + try { + HttpResult httpResult = HttpExecutorFactory.getInstance().doGet(httpUrl, params, header, 3000); + if (httpResult == null || httpResult.getStatusCode() != 200) { LOGGER.warn("remove vGroup in old cluster failed"); return new Result<>( - String.valueOf(closeableHttpResponse.getStatusLine().getStatusCode()), + String.valueOf(httpResult != null ? httpResult.getStatusCode() : 0), "removing vGroup " + vGroup + " in old cluster " + clusterName + " failed"); } LOGGER.info( diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/MockHttpExecutor.java b/namingserver/src/test/java/org/apache/seata/namingserver/MockHttpExecutor.java new file mode 100644 index 00000000000..1050f16dbe6 --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/MockHttpExecutor.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.namingserver; + +import org.apache.seata.common.http.HttpExecutor; +import org.apache.seata.common.http.HttpResult; +import org.apache.seata.common.loader.LoadLevel; + +import java.io.IOException; +import java.util.Map; + +@LoadLevel(name = "Http2", order = 2) +public class MockHttpExecutor implements HttpExecutor { + @Override + public HttpResult doPost(String url, Map params, Map header, int timeout) + throws IOException { + HttpResult result = new HttpResult(); + result.setStatusCode(200); + return result; + } + + @Override + public HttpResult doPost(String url, String body, Map header, int timeout) throws IOException { + HttpResult result = new HttpResult(); + result.setStatusCode(200); + return result; + } + + @Override + public HttpResult doGet(String url, Map param, Map header, int timeout) + throws IOException { + HttpResult result = new HttpResult(); + result.setStatusCode(200); + return result; + } +} diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java index 4ac96eacd93..c4df70f64fa 100644 --- a/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java +++ b/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java @@ -16,15 +16,15 @@ */ package org.apache.seata.namingserver; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.seata.common.http.HttpExecutor; +import org.apache.seata.common.http.HttpExecutorFactory; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.common.metadata.Cluster; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.metadata.namingserver.NamingServerNode; import org.apache.seata.common.metadata.namingserver.Unit; import org.apache.seata.common.result.Result; -import org.apache.seata.common.util.HttpClientUtil; import org.apache.seata.namingserver.entity.vo.monitor.ClusterVO; import org.apache.seata.namingserver.listener.ClusterChangeEvent; import org.apache.seata.namingserver.manager.NamingManager; @@ -38,6 +38,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.test.util.ReflectionTestUtils; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -64,12 +65,9 @@ class NamingManagerTest { private ApplicationContext applicationContext; @Mock - private CloseableHttpResponse httpResponse; + private HttpExecutor httpExecutor; - @Mock - private StatusLine statusLine; - - private MockedStatic mockedHttpClientUtil; + private MockedStatic mockedHttpExecutorFactory; @BeforeEach void setUp() { @@ -78,11 +76,8 @@ void setUp() { ReflectionTestUtils.setField(namingManager, "heartbeatTimeThreshold", 500000); ReflectionTestUtils.setField(namingManager, "heartbeatCheckTimePeriod", 10000000); - Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine); - mockedHttpClientUtil = Mockito.mockStatic(HttpClientUtil.class); - mockedHttpClientUtil - .when(() -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt())) - .thenReturn(httpResponse); + mockedHttpExecutorFactory = Mockito.mockStatic(HttpExecutorFactory.class); + mockedHttpExecutorFactory.when(HttpExecutorFactory::getInstance).thenReturn(httpExecutor); namingManager.init(); } @@ -102,8 +97,8 @@ private NamingServerNode createTestNode(String host, int port, String unitName) @AfterEach void tearDown() { - if (mockedHttpClientUtil != null) { - mockedHttpClientUtil.close(); + if (mockedHttpExecutorFactory != null) { + mockedHttpExecutorFactory.close(); } } @@ -216,7 +211,7 @@ void testInstanceHeartBeatCheck() { } @Test - void testCreateGroup() { + void testCreateGroup() throws IOException { String namespace = "test-namespace"; String clusterName = "test-cluster"; String unitName = UUID.randomUUID().toString(); @@ -228,14 +223,17 @@ void testCreateGroup() { node.getMetadata().put(CONSTANT_GROUP, vGroups); namingManager.registerInstance(node, namespace, clusterName, unitName); - Mockito.when(statusLine.getStatusCode()).thenReturn(200); + HttpResult mockResult = new HttpResult(); + mockResult.setStatusCode(200); + Mockito.when(httpExecutor.doGet(anyString(), anyMap(), anyMap(), anyInt())) + .thenReturn(mockResult); + Result result = namingManager.createGroup(namespace, vGroup, clusterName, unitName); assertTrue(result.isSuccess()); assertEquals("200", result.getCode()); assertEquals("add vGroup successfully!", result.getMessage()); - mockedHttpClientUtil.verify( - () -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt()), Mockito.times(1)); + Mockito.verify(httpExecutor, Mockito.times(1)).doGet(anyString(), anyMap(), anyMap(), anyInt()); } @Test @@ -274,7 +272,7 @@ void testAddGroup() { } @Test - void testRemoveGroup() { + void testRemoveGroup() throws IOException { String namespace = "test-namespace"; String clusterName = "test-cluster"; String unitName = UUID.randomUUID().toString(); @@ -289,12 +287,10 @@ void testRemoveGroup() { nodeList.add(node); unit.setNamingInstanceList(nodeList); - Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine); - Mockito.when(statusLine.getStatusCode()).thenReturn(200); - - mockedHttpClientUtil - .when(() -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt())) - .thenReturn(httpResponse); + HttpResult mockResult = new HttpResult(); + mockResult.setStatusCode(200); + Mockito.when(httpExecutor.doGet(anyString(), anyMap(), anyMap(), anyInt())) + .thenReturn(mockResult); Result result = namingManager.removeGroup(unit, vGroup, clusterName, namespace, unitName); @@ -302,8 +298,7 @@ void testRemoveGroup() { assertEquals("200", result.getCode()); assertEquals("remove group in old cluster successfully!", result.getMessage()); - mockedHttpClientUtil.verify( - () -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt()), Mockito.times(1)); + Mockito.verify(httpExecutor, Mockito.times(1)).doGet(anyString(), anyMap(), anyMap(), anyInt()); } @Test diff --git a/namingserver/src/test/resources/META-INF/services/org.apache.seata.common.http.HttpExecutor b/namingserver/src/test/resources/META-INF/services/org.apache.seata.common.http.HttpExecutor new file mode 100644 index 00000000000..f690fc56e34 --- /dev/null +++ b/namingserver/src/test/resources/META-INF/services/org.apache.seata.common.http.HttpExecutor @@ -0,0 +1,17 @@ +# +# 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. +# +org.apache.seata.namingserver.MockHttpExecutor \ No newline at end of file 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 ecf50093afe..375e7d0143f 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 @@ 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 @@ protected void doInitResourceId(DataSourceProxy proxy) { proxy.setResourceId(resourceId); } - @NotNull private static StringBuilder getParamsBuilder(String resourceId) { StringBuilder paramsBuilder = new StringBuilder(); String paramUrl = resourceId.substring(resourceId.indexOf(';') + 1); diff --git a/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java b/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java index 5c3b360db2f..4f604ed775f 100644 --- a/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java +++ b/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java @@ -17,12 +17,11 @@ package org.apache.seata.server.controller; import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.ContentType; import org.apache.http.protocol.HTTP; import org.apache.seata.common.holder.ObjectHolder; -import org.apache.seata.common.util.HttpClientUtil; +import org.apache.seata.common.http.Http1HttpExecutor; +import org.apache.seata.common.http.HttpResult; import org.apache.seata.server.BaseSpringBootTest; import org.apache.seata.server.cluster.listener.ClusterChangeEvent; import org.junit.jupiter.api.Assertions; @@ -63,14 +62,15 @@ void watchTimeoutTest() throws Exception { header.put(HTTP.CONN_KEEP_ALIVE, "close"); Map param = new HashMap<>(); param.put("default-test", "1"); - try (CloseableHttpResponse response = HttpClientUtil.doPost( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", param, header, 5000)) { - if (response != null) { - StatusLine statusLine = response.getStatusLine(); - Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, statusLine.getStatusCode()); - return; - } + + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", param, header, 5000); + + if (httpResult != null) { + Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, httpResult.getStatusCode()); + return; } + Assertions.fail(); } @@ -94,14 +94,15 @@ public void run() { } }); thread.start(); - try (CloseableHttpResponse response = - HttpClientUtil.doPost("http://127.0.0.1:" + port + "/metadata/v1/watch", param, header, 30000)) { - if (response != null) { - StatusLine statusLine = response.getStatusLine(); - Assertions.assertEquals(HttpStatus.SC_OK, statusLine.getStatusCode()); - return; - } + + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://127.0.0.1:" + port + "/metadata/v1/watch", param, header, 30000); + if (httpResult != null) { + + Assertions.assertEquals(HttpStatus.SC_OK, httpResult.getStatusCode()); + return; } + Assertions.fail(); } @@ -111,15 +112,15 @@ void testXssFilterBlocked_queryParam() throws Exception { String malicious = ""; Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - try (CloseableHttpResponse response = HttpClientUtil.doGet( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000&testParam=" - + URLEncoder.encode(malicious, String.valueOf(StandardCharsets.UTF_8)), - new HashMap<>(), - header, - 5000)) { - Assertions.assertEquals( - HttpStatus.SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); - } + + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doGet( + "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000&testParam=" + + URLEncoder.encode(malicious, String.valueOf(StandardCharsets.UTF_8)), + new HashMap<>(), + header, + 5000); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResult.getStatusCode()); } @Test @@ -131,11 +132,9 @@ void testXssFilterBlocked_formParam() throws Exception { Map params = new HashMap<>(); params.put("testParam", ""); - try (CloseableHttpResponse response = HttpClientUtil.doPost( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", params, headers, 5000)) { - Assertions.assertEquals( - HttpStatus.SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); - } + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", params, headers, 5000); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResult.getStatusCode()); } @Test @@ -146,11 +145,9 @@ void testXssFilterBlocked_jsonBody() throws Exception { String jsonBody = "{\"testParam\":\"\"}"; - try (CloseableHttpResponse response = HttpClientUtil.doPostJson( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", jsonBody, headers, 5000)) { - Assertions.assertEquals( - HttpStatus.SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); - } + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", jsonBody, headers, 5000); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResult.getStatusCode()); } @Test @@ -163,11 +160,9 @@ void testXssFilterBlocked_headerParam() throws Exception { Map params = new HashMap<>(); params.put("safeParam", "123"); - try (CloseableHttpResponse response = HttpClientUtil.doPost( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", params, headers, 5000)) { - Assertions.assertEquals( - HttpStatus.SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); - } + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", params, headers, 5000); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResult.getStatusCode()); } @Test @@ -179,15 +174,15 @@ void testXssFilterBlocked_multiSource() throws Exception { String jsonBody = "{\"testParam\":\"\"}"; - try (CloseableHttpResponse response = HttpClientUtil.doPostJson( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000&urlParam=" - + URLEncoder.encode("", String.valueOf(StandardCharsets.UTF_8)), - jsonBody, - headers, - 5000)) { - Assertions.assertEquals( - HttpStatus.SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); - } + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost( + "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000&urlParam=" + + URLEncoder.encode( + "", String.valueOf(StandardCharsets.UTF_8)), + jsonBody, + headers, + 5000); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResult.getStatusCode()); } @Test @@ -199,10 +194,8 @@ void testXssFilterBlocked_formParamWithUserCustomKeyWords() throws Exception { Map params = new HashMap<>(); params.put("testParam", "custom1"); - try (CloseableHttpResponse response = HttpClientUtil.doPost( - "http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", params, headers, 5000)) { - Assertions.assertEquals( - HttpStatus.SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); - } + HttpResult httpResult = Http1HttpExecutor.getInstance() + .doPost("http://127.0.0.1:" + port + "/metadata/v1/watch?timeout=3000", params, headers, 5000); + Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, httpResult.getStatusCode()); } }