headers) {
+ this.headers = headers;
+ }
+
+ /**
+ * Sets the url.
+ * You can use getUrl() to get the value of url
+ *
+ * @param url url
+ */
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ /**
+ * Sets the method.
+ * You can use getMethod() to get the value of method
+ *
+ * @param method method
+ */
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ /**
+ * Sets the readTimeOut.
+ * You can use getReadTimeOut() to get the value of readTimeOut
+ *
+ * @param readTimeOut readTimeOut
+ */
+ public void setReadTimeOut(long readTimeOut) {
+ this.readTimeOut = readTimeOut;
+ }
+
+ /**
+ * Sets the timeUnit.
+ * You can use getTimeUnit() to get the value of timeUnit
+ *
+ * @param timeUnit timeUnit
+ */
+ public void setTimeUnit(TimeUnit timeUnit) {
+ this.timeUnit = timeUnit;
+ }
+
+ /**
+ * Sets the retry.
+ * You can use getRetry() to get the value of retry
+ *
+ * @param retry retry
+ */
+ public void setRetry(int retry) {
+ this.retry = retry;
+ }
+
+ /**
+ * Sets the reqSerializer.
+ * You can use getReqSerializer() to get the value of reqSerializer
+ *
+ * @param reqSerializer reqSerializer
+ */
+ public void setReqSerializer(SerializerEnum reqSerializer) {
+ this.reqSerializer = reqSerializer;
+ }
+
+ /**
+ * Sets the repSerializer.
+ * You can use getRepSerializer() to get the value of repSerializer
+ *
+ * @param repSerializer repSerializer
+ */
+ public void setRepSerializer(SerializerEnum repSerializer) {
+ this.repSerializer = repSerializer;
+ }
+
+ /**
+ * Sets the isEncode.
+ * You can use getEncode() to get the value of isEncode
+ *
+ * @param encode encode
+ */
+ public void setEncode(boolean encode) {
+ isEncode = encode;
+ }
+
+ /**
+ * Get the body content as byte array
+ *
+ * @return the byte array
+ */
+ public byte[] getBodyAsBytes() {
+ byte[] dataBytes = new byte[0];
+ if (null != this.getBody()) {
+ dataBytes = this.getReqSerializer().getSerializer().serialize(this.getBody()).getBytes();
+ }
+ return dataBytes;
+ }
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/RemoteException.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/RemoteException.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd78aef50cd3f5ad94b5f02c1abc0069e372089d
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/RemoteException.java
@@ -0,0 +1,116 @@
+package cn.icanci.rec.engine.script.client;
+
+import java.io.IOException;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:15
+ */
+public class RemoteException extends IOException {
+ /** 序列化版本 */
+ private static final long serialVersionUID = -850230685842125442L;
+
+ private int code = -1;
+
+ /**
+ * Constructs a new exception with the specified detail message. The
+ * cause is not initialized, and may subsequently be initialized by
+ * a call to {@link #initCause}.
+ *
+ * @param code error code
+ * @param message the detail message. The detail message is saved for
+ * later retrieval by the {@link #getMessage()} method.
+ */
+ public RemoteException(int code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message. The
+ * cause is not initialized, and may subsequently be initialized by
+ * a call to {@link #initCause}.
+ *
+ * @param message the detail message. The detail message is saved for
+ * later retrieval by the {@link #getMessage()} method.
+ */
+ public RemoteException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and
+ * cause. Note that the detail message associated with
+ * {@code cause} is not automatically incorporated in
+ * this exception's detail message.
+ *
+ * @param code error code
+ * @param message the detail message (which is saved for later retrieval
+ * by the {@link #getMessage()} method).
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A null value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.)
+ * @since 1.4
+ */
+ public RemoteException(int code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and
+ * cause.
Note that the detail message associated with
+ * {@code cause} is not automatically incorporated in
+ * this exception's detail message.
+ *
+ * @param message the detail message (which is saved for later retrieval
+ * by the {@link #getMessage()} method).
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A null value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.)
+ * @since 1.4
+ */
+ public RemoteException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail
+ * message of (cause==null ? null : cause.toString()) (which
+ * typically contains the class and detail message of cause).
+ * This constructor is useful for exceptions that are little more than
+ * wrappers for other throwables (for example, {@link
+ * PrivilegedActionException}).
+ *
+ * @param code error code
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A null value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.)
+ * @since 1.4
+ */
+ public RemoteException(int code, Throwable cause) {
+ super(cause);
+ this.code = code;
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail
+ * message of (cause==null ? null : cause.toString()) (which
+ * typically contains the class and detail message of cause).
+ * This constructor is useful for exceptions that are little more than
+ * wrappers for other throwables (for example, {@link
+ * PrivilegedActionException}).
+ *
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A null value is
+ * permitted, and indicates that the cause is nonexistent or
+ * unknown.)
+ * @since 1.4
+ */
+ public RemoteException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/ByteArrayMessageConverter.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/ByteArrayMessageConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..492ccaba187470bbee0e02d0d1b67be6d9f54eb7
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/ByteArrayMessageConverter.java
@@ -0,0 +1,33 @@
+package cn.icanci.rec.engine.script.client.http;
+
+import okhttp3.Response;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+class ByteArrayMessageConverter implements MessageConverter {
+ /**
+ * Read internal t
+ *
+ * @param response the response
+ * @return the de-serialized data
+ */
+ @Override
+ public byte[] readInternal(Response response) {
+ long contentLength = response.body().contentLength();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(contentLength >= 0L ? (int) contentLength : 4096);
+ try {
+ IOUtils.copy(response.body().byteStream(), bos);
+ return bos.toByteArray();
+ } catch (IOException ignore) {
+ // no op
+ }
+ return null;
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/HttpMethod.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/HttpMethod.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecc5cc0e9efca6aa27de525a9ce7af9e11255fd2
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/HttpMethod.java
@@ -0,0 +1,43 @@
+package cn.icanci.rec.engine.script.client.http;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:28
+ */
+public enum HttpMethod {
+
+ GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
+
+ private static final Map mappings = new HashMap<>(16);
+
+ static {
+ for (HttpMethod httpMethod : values()) {
+ mappings.put(httpMethod.name(), httpMethod);
+ }
+ }
+
+ /**
+ * Resolve the given method value to an {@code HttpMethod}.
+ * @param method the method value as a String
+ * @return the corresponding {@code HttpMethod}, or {@code null} if not found
+ * @since 4.2.4
+ */
+ public static HttpMethod resolve(String method) {
+ return (method != null ? mappings.get(method) : null);
+ }
+
+ /**
+ * Determine whether this {@code HttpMethod} matches the given
+ * method value.
+ * @param method the method value as a String
+ * @return {@code true} if it matches, {@code false} otherwise
+ * @since 4.2.4
+ */
+ public boolean matches(String method) {
+ return (this == resolve(method));
+ }
+
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/MessageConverter.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/MessageConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..52755a5f9c7d612e9633202610297b4d9bf9e3a0
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/MessageConverter.java
@@ -0,0 +1,18 @@
+package cn.icanci.rec.engine.script.client.http;
+
+import okhttp3.Response;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+public interface MessageConverter {
+
+ /**
+ * Read internal t
+ *
+ * @param response the response
+ * @return the de-serialized data
+ */
+ T readInternal(Response response);
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/OkHttpClientImpl.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/OkHttpClientImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..415604d038e3f09b7149cfc062a202ce403dabbb
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/OkHttpClientImpl.java
@@ -0,0 +1,240 @@
+package cn.icanci.rec.engine.script.client.http;
+
+import cn.icanci.rec.engine.script.client.AbstractRetryClient;
+import cn.icanci.rec.engine.script.client.Client;
+import cn.icanci.rec.engine.script.client.http.interceptor.HttpCodeInterceptor;
+import cn.icanci.rec.engine.script.client.http.interceptor.NetworkInterceptor;
+import cn.icanci.rec.engine.script.client.serializer.Serializer;
+import cn.icanci.rec.engine.script.client.serializer.SerializerEnum;
+import okhttp3.*;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+@SuppressWarnings("all")
+public class OkHttpClientImpl extends AbstractRetryClient {
+ private static final long DEFAULT_TIMEOUT = 3;
+ private static final TimeUnit DEFAULT_TIMEUNIT = TimeUnit.SECONDS;
+ private static final OkHttpClient CLIENT = new OkHttpClient();
+ private static final Map CLIENTS = new ConcurrentHashMap<>();
+ private static final Map CONVERTER_MAP = new HashMap<>();
+ private static final Interceptor NETWORK_INTERCEPTOR = new NetworkInterceptor();
+ private static final Interceptor HTTP_CODE_INTERCEPTOR = new HttpCodeInterceptor();
+
+ static {
+ CONVERTER_MAP.put(String.class, new StringMessageConverter());
+ CONVERTER_MAP.put(byte[].class, new ByteArrayMessageConverter());
+ }
+
+ /**
+ * Get the instance of client
+ *
+ * @return the instance of client
+ */
+ public static Client getInstance() {
+ return OkHttpClientImplHolder.CLIENT_IMPL;
+ }
+
+ /**
+ * Do execute v.
+ *
+ * @param request the request
+ * @param clazz the clazz
+ * @return the v
+ */
+ @Override
+ protected V doExecute(RpcRequest request, Class clazz) throws IOException {
+ Request.Builder builder = new Request.Builder();
+ addHeaders(request, builder);
+
+ try {
+ switch (request.getMethod()) {
+ case "GET":
+ String url = buildGetInfo(request);
+ builder.url(url).get();
+ return doExecute(request, builder.build(), clazz);
+
+ case "POST":
+ RequestBody content = buildPostInfo(request);
+ builder.url(request.getUrl()).post(content);
+ return doExecute(request, builder.build(), clazz);
+
+ case "PUT":
+ case "DELETE":
+ default:
+ throw new UnsupportedOperationException("HTTP method: " + request.getMethod());
+
+ }
+ } catch (IOException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * 构建 POST 方法请求参数
+ *
+ * @param request http 请求
+ * @return http 请求数据
+ */
+ protected RequestBody buildPostInfo(RpcRequest request) {
+ MediaType mediaType = MediaType.parse(request.getMediaType());
+ Object param = request.getBody();
+ if (param == null) {
+ return RequestBody.create(mediaType, StringUtils.EMPTY);
+ }
+ if (String.class.isAssignableFrom(param.getClass())) {
+ return RequestBody.create(mediaType, String.class.cast(param));
+ }
+
+ if (request.getReqSerializer() == null) {
+ throw new IllegalArgumentException("The serializer is required for type " + param.getClass());
+ }
+ Serializer serializer = request.getReqSerializer().getSerializer();
+ String dataString = serializer.serialize(param);
+ return RequestBody.create(mediaType, dataString);
+ }
+
+ /**
+ * 构建 GET 方法请求参数
+ *
+ * @param request http 请求
+ * @return GET 方法请求参数
+ * @throws UnsupportedEncodingException 异常
+ */
+ protected String buildGetInfo(RpcRequest request) throws UnsupportedEncodingException {
+ String url = request.getUrl();
+ if (StringUtils.isBlank(request.getBody().toString())) {
+ return url;
+ }
+ Map params = (Map) request.getBody();
+ boolean encoded = request.isEncode();
+
+ if (MapUtils.isNotEmpty(params)) {
+ StringBuilder sb = new StringBuilder();
+ if (encoded) {
+ for (Map.Entry param : params.entrySet()) {
+ sb.append(param.getKey()).append("=").append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8.name()));
+ sb.append("&");
+ }
+ } else {
+ for (Map.Entry param : params.entrySet()) {
+ sb.append(param.getKey()).append("=").append(param.getValue());
+ sb.append("&");
+ }
+ }
+ String paramStr = StringUtils.stripEnd(sb.toString(), "&");
+ url = url + "?" + paramStr;
+ }
+ return url;
+ }
+
+ /**
+ * http calling
+ *
+ * @param rpcRequest rpc request
+ * @param request http request
+ * @param clazz response class
+ * @param the response type
+ * @return response object
+ * @throws IOException io 异常
+ */
+ private V doExecute(RpcRequest rpcRequest, Request request, Class clazz) throws IOException {
+ OkHttpClient client = getClient(rpcRequest.getReadTimeOut(), rpcRequest.getTimeUnit());
+ Response response = client.newCall(request).execute();
+ if (clazz != String.class || clazz != byte[].class) {
+ String metaData = (String) CONVERTER_MAP.get(String.class).readInternal(response);
+ SerializerEnum serializerEnum = rpcRequest.getRepSerializer();
+ return serializerEnum == null ? (V) metaData : serializerEnum.getSerializer().deserialize(metaData, clazz);
+ }
+ return (V) CONVERTER_MAP.get(clazz).readInternal(response);
+ }
+
+ /**
+ * 构建 HTTP 头信息
+ *
+ * @param request http request
+ * @param builder request builder
+ */
+ private void addHeaders(RpcRequest request, Request.Builder builder) {
+ Map headers = request.getHeaders();
+ if (MapUtils.isEmpty(headers)) {
+ return;
+ }
+ for (Map.Entry item : request.getHeaders().entrySet()) {
+ builder.addHeader(item.getKey(), item.getValue());
+ }
+ }
+
+ /**
+ * 获取客户端连接
+ *
+ * @return okhttp client
+ */
+ private OkHttpClient getClient(long timeout, TimeUnit timeUnit) {
+ if (timeout == 0) {
+ timeout = DEFAULT_TIMEOUT;
+ timeUnit = DEFAULT_TIMEUNIT;
+ }
+
+ String key = buildClientKey(timeout, timeUnit);
+
+ OkHttpClient client = CLIENTS.get(key);
+ if (client == null) {
+ client = newClient(timeout, timeUnit);
+ }
+ return client;
+ }
+
+ /**
+ * 创建新客户端
+ *
+ * @param timeout the timeout
+ * @param timeUnit the unit of timeout
+ * @return the okhttp client instance
+ */
+ private OkHttpClient newClient(long timeout, TimeUnit timeUnit) {
+ OkHttpClient.Builder builder = CLIENT.newBuilder();
+ builder.connectTimeout(timeout, timeUnit);
+ builder.readTimeout(timeout, timeUnit);
+ builder.writeTimeout(timeout, timeUnit);
+ builder.connectionPool(new ConnectionPool(64, 180, TimeUnit.SECONDS));
+ builder.addInterceptor(HTTP_CODE_INTERCEPTOR);
+ builder.addNetworkInterceptor(NETWORK_INTERCEPTOR);
+ OkHttpClient client = builder.build();
+ String key = buildClientKey(timeout, timeUnit);
+ CLIENTS.putIfAbsent(key, client);
+ return client;
+ }
+
+ /**
+ * Build client key
+ * timeout + timeout unit
+ *
+ * @param timeout 超时时间
+ * @param timeUnit 超时时间单位
+ * @return key
+ */
+ private static String buildClientKey(long timeout, TimeUnit timeUnit) {
+ return timeout + timeUnit.toString();
+ }
+
+ /**
+ * Ok http client holder
+ */
+ private static final class OkHttpClientImplHolder {
+ private static final OkHttpClientImpl CLIENT_IMPL = new OkHttpClientImpl();
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/StringMessageConverter.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/StringMessageConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbab9c9fdbbb3afc9a51632faabb45a285372733
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/StringMessageConverter.java
@@ -0,0 +1,30 @@
+package cn.icanci.rec.engine.script.client.http;
+
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+class StringMessageConverter implements MessageConverter {
+ /**
+ * Read internal t
+ *
+ * @param response the response
+ * @return the de-serialized data
+ */
+ @Override
+ public String readInternal(Response response) {
+ ResponseBody body = response.body();
+ try {
+ return body.string();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (body != null) {
+ body.close();
+ }
+ }
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/interceptor/HttpCodeInterceptor.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/interceptor/HttpCodeInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4841eee77d5da5134fccba1721a7580a64f4edb
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/interceptor/HttpCodeInterceptor.java
@@ -0,0 +1,45 @@
+package cn.icanci.rec.engine.script.client.http.interceptor;
+
+import cn.icanci.rec.engine.script.client.RemoteException;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:28
+ */
+public class HttpCodeInterceptor implements Interceptor {
+ private static final String FORMAT = "[%s] %s: %s";
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request request = chain.request();
+ Response response = chain.proceed(request);
+ int code = response.code();
+ boolean success = isSuccess(code);
+ if (success) {
+ return response;
+ }
+
+ String errMsg = response.message();
+ String body = response.body().string();
+ String error = String.format(FORMAT, code, errMsg, body);
+ throw new RemoteException(code, error);
+ }
+
+ /**
+ * 判断 http 调用是否成功
+ *
+ * @param code http 编码
+ * @return http 调用是否成功
+ */
+ private boolean isSuccess(int code) {
+ String httpCode = String.valueOf(code);
+ return StringUtils.startsWith(httpCode, "2") || StringUtils.startsWith(httpCode, "3");
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/interceptor/NetworkInterceptor.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/interceptor/NetworkInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..82d5f5eab9e84f4fd775afdff878c39216809642
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/http/interceptor/NetworkInterceptor.java
@@ -0,0 +1,54 @@
+package cn.icanci.rec.engine.script.client.http.interceptor;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:28
+ */
+public class NetworkInterceptor implements Interceptor {
+ /** Client network log */
+ private static final Logger LOG = LoggerFactory.getLogger("MERCHANT-CLIENT-LOGGER");
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ long start = System.currentTimeMillis();
+ Request request = chain.request();
+
+ // process request
+ String url = null;
+ String method = null;
+
+ try {
+ url = request.url().toString();
+ method = request.method();
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ } finally {
+ LOG.info("[REQUEST] URL={}, METHOD={}", url, method);
+ }
+
+ // delegate the request to okhttp
+ Response response = chain.proceed(request);
+
+ // process response
+ String code = null;
+ try {
+ code = String.valueOf(response.code());
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ } finally {
+ long end = System.currentTimeMillis();
+ String duration = String.valueOf(end - start);
+ LOG.info("[RESPONSE] URL={}, RT={}, CODE={}", url, duration, code);
+ }
+ return response;
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/FastJsonSerializer.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/FastJsonSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..7471d0310627dcaba90935ae1794ff39bb92f2f9
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/FastJsonSerializer.java
@@ -0,0 +1,32 @@
+package cn.icanci.rec.engine.script.client.serializer;
+
+import cn.icanci.rec.common.utils.FastJsonUtils;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+public class FastJsonSerializer implements Serializer {
+ /**
+ * 反序列化
+ *
+ * @param json JSON 结构数据
+ * @param clazz 待反序列化类结构
+ * @return 反序列化实例
+ */
+ @Override
+ public T deserialize(String json, Class clazz) {
+ return FastJsonUtils.fromJSONString(json, clazz);
+ }
+
+ /**
+ * 序列化
+ *
+ * @param t 待序列化数据
+ * @return JSON 结构数据
+ */
+ @Override
+ public String serialize(T t) {
+ return FastJsonUtils.toJSONString(t);
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/NativeFastJsonSerializer.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/NativeFastJsonSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..9009d4a84779ec075dbcb5f1f633d55a65d401c3
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/NativeFastJsonSerializer.java
@@ -0,0 +1,32 @@
+package cn.icanci.rec.engine.script.client.serializer;
+
+import com.alibaba.fastjson.JSON;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+public class NativeFastJsonSerializer implements Serializer {
+ /**
+ * 反序列化
+ *
+ * @param json JSON 结构数据
+ * @param clazz 待反序列化类结构
+ * @return 反序列化实例
+ */
+ @Override
+ public T deserialize(String json, Class clazz) {
+ return JSON.parseObject(json, clazz);
+ }
+
+ /**
+ * 序列化
+ *
+ * @param t 待序列化数据
+ * @return JSON 结构数据
+ */
+ @Override
+ public String serialize(T t) {
+ return JSON.toJSONString(t);
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/Serializer.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/Serializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce579b5e93373459b2c6fa918b2134bfda6a3e0c
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/Serializer.java
@@ -0,0 +1,27 @@
+package cn.icanci.rec.engine.script.client.serializer;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+public interface Serializer {
+
+ /**
+ * 反序列化
+ *
+ * @param json JSON 结构数据
+ * @param clazz 待反序列化类结构
+ * @param 待反序列化类型
+ * @return 反序列化实例
+ */
+ T deserialize(String json, Class clazz);
+
+ /**
+ * 序列化
+ *
+ * @param t 待序列化数据
+ * @param 待序列化数据类型
+ * @return JSON 结构数据
+ */
+ String serialize(T t);
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/SerializerEnum.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/SerializerEnum.java
new file mode 100644
index 0000000000000000000000000000000000000000..a90ebc7010640222ceafe9b77e6bd477f6f6ff4a
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/SerializerEnum.java
@@ -0,0 +1,91 @@
+package cn.icanci.rec.engine.script.client.serializer;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+public enum SerializerEnum {
+ /**
+ * FASTJSON
+ */
+ FASTJSON("FASTJSON", new FastJsonSerializer()),
+ /**
+ * NATIVE-FASTJSON
+ */
+ NATIVE_FASTJSON("NATIVE-FASTJSON", new NativeFastJsonSerializer()),
+ /**
+ * STRING
+ */
+ STRING("STRING", new StringSerializer()),
+
+ ;
+
+ /**
+ * 序列化方式
+ */
+ private String code;
+
+ /**
+ * 序列化器
+ */
+ private Serializer serializer;
+
+ /**
+ * 私有构造方法
+ *
+ * @param code 序列化方式
+ * @param serializer 序列化器
+ */
+ SerializerEnum(String code, Serializer serializer) {
+ this.code = code;
+ this.serializer = serializer;
+ }
+
+ /**
+ * 根据序列化方式获取序列化器
+ *
+ * @param code 序列化方式
+ * @return 序列化器
+ */
+ public static Serializer getByCode(String code) {
+ for (SerializerEnum e : SerializerEnum.values()) {
+ if (e.getCode().equals(code)) {
+ return e.getSerializer();
+ }
+ }
+ throw new IllegalArgumentException("The serializer code [" + code + "] is not supported.");
+ }
+
+ /**
+ * 根据序列化方式获取序列化器
+ *
+ * @param code 序列化方式
+ * @return 序列化器
+ */
+ public static SerializerEnum toEnum(String code) {
+ for (SerializerEnum e : SerializerEnum.values()) {
+ if (e.getCode().equals(code)) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("The serializer code [" + code + "] is not supported.");
+ }
+
+ /**
+ * 获取序列化方式
+ *
+ * @return 序列化方式
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * 获取序列化器
+ *
+ * @return 序列化器
+ */
+ public Serializer getSerializer() {
+ return serializer;
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/StringSerializer.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/StringSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bd76169c3aae74bd19ca552864add572abb5ae9
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/client/serializer/StringSerializer.java
@@ -0,0 +1,30 @@
+package cn.icanci.rec.engine.script.client.serializer;
+
+/**
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:14
+ */
+public class StringSerializer implements Serializer {
+ /**
+ * 反序列化
+ *
+ * @param json JSON 结构数据
+ * @param clazz 待反序列化类结构
+ * @return 反序列化实例
+ */
+ @Override
+ public T deserialize(String json, Class clazz) {
+ return (T) json;
+ }
+
+ /**
+ * 序列化
+ *
+ * @param t 待序列化数据
+ * @return JSON 结构数据
+ */
+ @Override
+ public String serialize(T t) {
+ return (String) t;
+ }
+}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java
index 89c9933ce2b86bb341c477a1d9b29cf748c35e8b..d7ac6591fd4285f9c789763c77f0c5f05bb2b93e 100644
--- a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java
@@ -1,17 +1,34 @@
package cn.icanci.rec.engine.script.impl;
+import cn.icanci.rec.common.enums.HttpRequestTypeEnum;
import cn.icanci.rec.common.enums.ScriptTypeEnum;
+import cn.icanci.rec.common.utils.FastJsonUtils;
import cn.icanci.rec.engine.script.RecScriptEngine;
+import cn.icanci.rec.engine.script.client.Client;
+import cn.icanci.rec.engine.script.client.http.HttpMethod;
+import cn.icanci.rec.engine.script.client.http.OkHttpClientImpl;
+import cn.icanci.rec.engine.script.client.serializer.SerializerEnum;
import cn.icanci.rec.engine.script.context.RecScriptEngineContext;
import cn.icanci.rec.engine.script.factory.ScriptEngineFactory;
+import cn.icanci.rec.engine.script.wrapper.HttpResponseWrapper;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import javax.script.*;
+import com.google.common.collect.Maps;
+
/**
* @author icanci
* @since 1.0 Created in 2022/11/12 22:46
*/
public class RecScriptEngineImpl implements RecScriptEngine {
+ /** http实例 */
+ private static final Client CLIENT = OkHttpClientImpl.getInstance();
+ /** DEFAULT_APPLICATION_JSON_VALUE */
+ public static final String DEFAULT_APPLICATION_JSON_VALUE = "application/json";
/**
* 执行脚本
@@ -111,4 +128,42 @@ public class RecScriptEngineImpl implements RecScriptEngine {
return context;
}
+ @Override
+ public HttpResponseWrapper httpEval(HttpRequestTypeEnum requestType, String reqUrl, String reqParam, int timeout) {
+
+ HttpMethod httpMethod = null;
+
+ switch (requestType) {
+ case GET:
+ httpMethod = HttpMethod.GET;
+ break;
+ case POST:
+ httpMethod = HttpMethod.POST;
+ break;
+ default:
+ // no op
+ }
+
+ if (httpMethod == null) {
+ throw new NullPointerException("HttpMethod is Null !");
+ }
+
+ if (timeout <= 0) {
+ throw new IllegalArgumentException("Http Request Timeout is Less than 0!");
+ }
+
+ HashMap headers = Maps.newHashMap();
+ Map reqMap = FastJsonUtils.fromJSONString(reqParam, Map.class);
+ Client.RpcRequest rpcRequest = new Client.RpcRequest(reqMap, DEFAULT_APPLICATION_JSON_VALUE, headers, reqUrl, httpMethod.name(), false, timeout, TimeUnit.SECONDS, 0,
+ SerializerEnum.FASTJSON, SerializerEnum.FASTJSON);
+
+ HttpResponseWrapper wrapper = new HttpResponseWrapper();
+ try {
+ wrapper.setResponse(CLIENT.call(rpcRequest, String.class));
+ } catch (Throwable e) {
+ wrapper.setException(e);
+ }
+ return wrapper;
+ }
+
}
diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/wrapper/HttpResponseWrapper.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/wrapper/HttpResponseWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5297c4c101ba22a3b91d413a2fd9b2edab6e1b12
--- /dev/null
+++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/wrapper/HttpResponseWrapper.java
@@ -0,0 +1,77 @@
+package cn.icanci.rec.engine.script.wrapper;
+
+import cn.icanci.rec.common.utils.FastJsonUtils;
+
+import java.util.Map;
+import java.util.StringJoiner;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Http请求结果Wrapper
+ *
+ * @author icanci
+ * @since 1.0 Created in 2022/11/14 22:36
+ */
+public class HttpResponseWrapper {
+ /**
+ * 执行结果
+ */
+ private String response;
+ /**
+ * 执行异常
+ */
+ private Throwable exception;
+
+ /**
+ * 判断返回是否是JSON
+ *
+ * @return 判断返回是否是JSON
+ */
+ public boolean isJson() {
+ return FastJsonUtils.isJson(response);
+ }
+
+ /**
+ * 获取返回结果的Map
+ *
+ * @return 返回Map
+ */
+ public Map getResponseForMap() {
+ if (response == null) {
+ return Maps.newHashMap();
+ }
+ return FastJsonUtils.fromJSONString(response, Map.class);
+ }
+
+ /**
+ * 是否执行成功
+ *
+ * @return 返回是否执行成功
+ */
+ public boolean isSuccess() {
+ return response != null || exception == null;
+ }
+
+ public String getResponse() {
+ return response;
+ }
+
+ public void setResponse(String response) {
+ this.response = response;
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public void setException(Throwable exception) {
+ this.exception = exception;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(",").add("response=" + response).add("exception=" + exception).toString();
+ }
+
+}
diff --git a/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java b/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java
index 1f711ede90f8312ad9b5bfb17e9a8d9139a1e3fd..bc303ce337239172e03e58415b10abec889ef36d 100644
--- a/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java
+++ b/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java
@@ -1,9 +1,14 @@
package cn.icanci.rec.engine.script.test;
+import cn.icanci.rec.common.enums.HttpRequestTypeEnum;
import cn.icanci.rec.common.enums.ScriptTypeEnum;
+import cn.icanci.rec.common.utils.FastJsonUtils;
import cn.icanci.rec.engine.script.RecScriptEngine;
import cn.icanci.rec.engine.script.RecScriptEngineManager;
import cn.icanci.rec.engine.script.context.RecScriptEngineContext;
+import cn.icanci.rec.engine.script.wrapper.HttpResponseWrapper;
+
+import java.io.Serializable;
import org.junit.Test;
@@ -21,4 +26,72 @@ public class RecScriptEngineManagerTest {
System.out.println(retVal);
System.out.println(context);
}
+
+ @Test
+ public void testRecScriptEngineManagerHttp() {
+ RecScriptEngine recScriptEngine = RecScriptEngineManager.getRecScriptEngine();
+ String reqUrl = "http://localhost:9110/dijiang/sys/config/query";
+ ConfigRequest request = new ConfigRequest();
+ request.setUk("xxx");
+ request.setEnv("test");
+ request.setFileName("hello");
+
+ HttpResponseWrapper eval = recScriptEngine.httpEval(HttpRequestTypeEnum.POST, reqUrl, FastJsonUtils.toJSONString(request), 3);
+ if (eval.isSuccess()) {
+ System.out.println(eval.isJson());
+ System.out.println(eval.getResponseForMap());
+ }
+ System.out.println(eval);
+ }
+
+ @Test
+ public void fastjsonIsJson() {
+// System.out.println(FastJsonUtils.isJson("「9999"));
+ System.out.println(FastJsonUtils.isJson("TRUE"));
+ System.out.println(FastJsonUtils.isJson("false "));
+// System.out.println(FastJsonUtils.isJson("11.1"));
+ System.out.println(Boolean.parseBoolean("true"));
+ System.out.println(Boolean.parseBoolean("false"));
+ }
+
+ private static class ConfigRequest implements Serializable {
+
+ private static final long serialVersionUID = 2380811183737977291L;
+ /**
+ * 项目唯一id
+ */
+ private String uk;
+ /**
+ * 环境
+ */
+ private String env;
+ /**
+ * 配置文件名字
+ */
+ private String fileName;
+
+ public String getUk() {
+ return uk;
+ }
+
+ public void setUk(String uk) {
+ this.uk = uk;
+ }
+
+ public String getEnv() {
+ return env;
+ }
+
+ public void setEnv(String env) {
+ this.env = env;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+ }
}