---初始化项目
This commit is contained in:
50
powerjob-remote/powerjob-remote-impl-http/pom.xml
Normal file
50
powerjob-remote/powerjob-remote-impl-http/pom.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>powerjob-remote</artifactId>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<version>5.1.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>powerjob-remote-impl-http</artifactId>
|
||||
<name>powerjob-remote-impl-http</name>
|
||||
<version>5.1.2</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<vertx.version>4.3.7</vertx.version>
|
||||
<powerjob-remote-framework.version>5.1.2</powerjob-remote-framework.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-remote-framework</artifactId>
|
||||
<version>${powerjob-remote-framework.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/io.vertx/vertx-core -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-core</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,17 @@
|
||||
package tech.powerjob.remote.http;
|
||||
|
||||
import tech.powerjob.remote.framework.transporter.Protocol;
|
||||
|
||||
/**
|
||||
* HttpProtocol
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2022/12/31
|
||||
*/
|
||||
public class HttpProtocol implements Protocol {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return tech.powerjob.common.enums.Protocol.HTTP.name();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package tech.powerjob.remote.http;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.http.HttpClient;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RequestBody;
|
||||
import io.vertx.ext.web.Route;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.remote.framework.actor.ActorInfo;
|
||||
import tech.powerjob.remote.framework.actor.HandlerInfo;
|
||||
import tech.powerjob.remote.framework.actor.ProcessType;
|
||||
import tech.powerjob.remote.framework.cs.CSInitializer;
|
||||
import tech.powerjob.remote.framework.cs.CSInitializerConfig;
|
||||
import tech.powerjob.remote.framework.transporter.Transporter;
|
||||
import tech.powerjob.remote.framework.utils.RemoteUtils;
|
||||
import tech.powerjob.remote.http.vertx.VertxInitializer;
|
||||
import tech.powerjob.remote.http.vertx.VertxTransporter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* HttpCSInitializer
|
||||
* 在纠结了1晚上后,最终决定选用 vertx 作为 http 底层,而不是直接使用 netty,理由如下:
|
||||
* - netty 实现容易,但性能调优方面需要时间成本和实践经验,而 vertx 作为 netty 的"嫡系"框架,对 netty 的封装理论上炉火纯青,性能不成问题
|
||||
* - vertx 唯一的缺点是其作为相对上层的框架,可能存在较为严重的包冲突问题,尤其是对于那些本身跑在 vertx-framework 上的用户
|
||||
* - 不过该问题可以通过更换协议解决,预计后续提供一个基于 netty 和自定义协议的实现
|
||||
*
|
||||
* 20240316 note:注意类名被强依赖,后续若有改动需要同步更改
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2022/12/31
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpVertxCSInitializer implements CSInitializer {
|
||||
|
||||
private Vertx vertx;
|
||||
private HttpServer httpServer;
|
||||
private HttpClient httpClient;
|
||||
|
||||
private CSInitializerConfig config;
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return tech.powerjob.common.enums.Protocol.HTTP.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(CSInitializerConfig config) {
|
||||
this.config = config;
|
||||
|
||||
// 【Vertx 版本升级时必须注意】临时解决 vertx 自带的 jackson 序列化无法支持字段升级问题(默认特性居然是不支持增删字段的序列化方式,外国框架也是一坨...)
|
||||
try {
|
||||
io.vertx.core.json.jackson.DatabindCodec.mapper()
|
||||
.configure(com.fasterxml.jackson.databind.MapperFeature.PROPAGATE_TRANSIENT_MARKER, true)
|
||||
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
|
||||
.configure(JsonParser.Feature.IGNORE_UNDEFINED, true)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
} catch (Throwable t) {
|
||||
log.warn("[HttpVertxCSInitializer] hack jackson failed!", t);
|
||||
}
|
||||
|
||||
vertx = VertxInitializer.buildVertx();
|
||||
httpServer = VertxInitializer.buildHttpServer(vertx);
|
||||
httpClient = VertxInitializer.buildHttpClient(vertx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transporter buildTransporter() {
|
||||
return new VertxTransporter(httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void bindHandlers(List<ActorInfo> actorInfos) {
|
||||
Router router = Router.router(vertx);
|
||||
// 处理请求响应
|
||||
router.route().handler(BodyHandler.create());
|
||||
actorInfos.forEach(actorInfo -> {
|
||||
Optional.ofNullable(actorInfo.getHandlerInfos()).orElse(Collections.emptyList()).forEach(handlerInfo -> {
|
||||
String handlerHttpPath = handlerInfo.getLocation().toPath();
|
||||
ProcessType processType = handlerInfo.getAnno().processType();
|
||||
|
||||
Handler<RoutingContext> routingContextHandler = buildRequestHandler(actorInfo, handlerInfo);
|
||||
Route route = router.post(handlerHttpPath);
|
||||
if (processType == ProcessType.BLOCKING) {
|
||||
route.blockingHandler(routingContextHandler, false);
|
||||
} else {
|
||||
route.handler(routingContextHandler);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 启动 vertx http server
|
||||
final int port = config.getBindAddress().getPort();
|
||||
final String host = config.getBindAddress().getHost();
|
||||
|
||||
httpServer.requestHandler(router)
|
||||
.exceptionHandler(e -> log.error("[PowerJob] unknown exception in Actor communication!", e))
|
||||
.listen(port, host)
|
||||
.toCompletionStage()
|
||||
.toCompletableFuture()
|
||||
.get(1, TimeUnit.MINUTES);
|
||||
|
||||
log.info("[PowerJobRemoteEngine] startup vertx HttpServer successfully!");
|
||||
}
|
||||
|
||||
private Handler<RoutingContext> buildRequestHandler(ActorInfo actorInfo, HandlerInfo handlerInfo) {
|
||||
Method method = handlerInfo.getMethod();
|
||||
Optional<Class<?>> powerSerializeClz = RemoteUtils.findPowerSerialize(method.getParameterTypes());
|
||||
|
||||
// 内部框架,严格模式,绑定失败直接报错
|
||||
if (ArrayUtils.isNotEmpty(method.getParameterTypes())) {
|
||||
if (!powerSerializeClz.isPresent()) {
|
||||
throw new PowerJobException("can't find any 'PowerSerialize' object in handler args: " + handlerInfo.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
return ctx -> {
|
||||
final RequestBody body = ctx.body();
|
||||
final Object convertResult = body.asPojo(powerSerializeClz.get());
|
||||
try {
|
||||
Object response = method.invoke(actorInfo.getActor(), convertResult);
|
||||
if (response != null) {
|
||||
if (response instanceof String) {
|
||||
ctx.end((String) response);
|
||||
} else {
|
||||
ctx.json(JsonObject.mapFrom(response));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.end();
|
||||
} catch (Throwable t) {
|
||||
// 注意这里是框架实际运行时,日志输出用标准 PowerJob 格式
|
||||
log.error("[PowerJob] invoke Handler[{}] failed!", handlerInfo.getLocation(), t);
|
||||
ctx.fail(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
httpClient.close();
|
||||
httpServer.close();
|
||||
vertx.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package tech.powerjob.remote.http.vertx;
|
||||
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import io.vertx.core.http.HttpClient;
|
||||
import io.vertx.core.http.HttpClientOptions;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.core.http.HttpServerOptions;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import tech.powerjob.common.OmsConstant;
|
||||
import tech.powerjob.common.PowerJobDKey;
|
||||
import tech.powerjob.common.utils.SysUtils;
|
||||
|
||||
/**
|
||||
* VertxInitializer
|
||||
* PowerJob 只是将 vertx 作为 toolkit 使用
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/1/1
|
||||
*/
|
||||
@Slf4j
|
||||
public class VertxInitializer {
|
||||
|
||||
/**
|
||||
* 默认开启长连接,且 75S 超时
|
||||
*/
|
||||
private static final int DEFAULT_KEEP_ALIVE_TIMEOUT = 75;
|
||||
|
||||
private static final int CONNECTION_TIMEOUT_MS = 3000;
|
||||
|
||||
private static final int SERVER_IDLE_TIMEOUT_S = 300;
|
||||
|
||||
public static Vertx buildVertx() {
|
||||
final int cpuCores = SysUtils.availableProcessors();
|
||||
VertxOptions options = new VertxOptions()
|
||||
.setWorkerPoolSize(Math.max(16, 2 * cpuCores))
|
||||
.setInternalBlockingPoolSize(Math.max(32, 4 * cpuCores));
|
||||
log.info("[PowerJob-Vertx] use vertx options: {}", options);
|
||||
return Vertx.vertx(options);
|
||||
}
|
||||
|
||||
public static HttpServer buildHttpServer(Vertx vertx) {
|
||||
HttpServerOptions httpServerOptions = new HttpServerOptions()
|
||||
.setIdleTimeout(SERVER_IDLE_TIMEOUT_S);
|
||||
tryEnableCompression(httpServerOptions);
|
||||
log.info("[PowerJob-Vertx] use HttpServerOptions: {}", httpServerOptions.toJson());
|
||||
return vertx.createHttpServer(httpServerOptions);
|
||||
}
|
||||
private static void tryEnableCompression(HttpServerOptions httpServerOptions) {
|
||||
// 非核心组件,不直接依赖类(无 import),加载报错可忽略
|
||||
try {
|
||||
httpServerOptions
|
||||
.addCompressor(io.netty.handler.codec.compression.StandardCompressionOptions.gzip())
|
||||
.setCompressionSupported(true);
|
||||
log.warn("[PowerJob-Vertx] enable server side compression successfully!");
|
||||
} catch (Throwable t) {
|
||||
log.warn("[PowerJob-Vertx] enable server side compression failed. The error is not fatal, but performance may be degraded", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpClient buildHttpClient(Vertx vertx) {
|
||||
|
||||
HttpClientOptions httpClientOptions = new HttpClientOptions()
|
||||
.setMetricsName(OmsConstant.PACKAGE)
|
||||
.setConnectTimeout(CONNECTION_TIMEOUT_MS)
|
||||
.setMaxPoolSize(Math.max(8, SysUtils.availableProcessors()) * 2);
|
||||
|
||||
// 长连接
|
||||
String keepaliveTimeout = System.getProperty(PowerJobDKey.TRANSPORTER_KEEP_ALIVE_TIMEOUT, String.valueOf(DEFAULT_KEEP_ALIVE_TIMEOUT));
|
||||
int keepaliveTimeoutInt = Integer.parseInt(keepaliveTimeout);
|
||||
if (keepaliveTimeoutInt > 0) {
|
||||
httpClientOptions.setKeepAlive(true).setKeepAliveTimeout(keepaliveTimeoutInt);
|
||||
} else {
|
||||
httpClientOptions.setKeepAlive(false);
|
||||
}
|
||||
|
||||
// 压缩判定
|
||||
String enableCompressing = System.getProperty(PowerJobDKey.TRANSPORTER_USE_COMPRESSING);
|
||||
if (StringUtils.isNotEmpty(enableCompressing)) {
|
||||
httpClientOptions.setTryUseCompression(StringUtils.equalsIgnoreCase(enableCompressing, Boolean.TRUE.toString()));
|
||||
}
|
||||
|
||||
log.info("[PowerJob-Vertx] use HttpClientOptions: {}", httpClientOptions.toJson());
|
||||
return vertx.createHttpClient(httpClientOptions);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package tech.powerjob.remote.http.vertx;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.http.HttpClient;
|
||||
import io.vertx.core.http.HttpClientRequest;
|
||||
import io.vertx.core.http.HttpClientResponse;
|
||||
import io.vertx.core.http.HttpMethod;
|
||||
import io.vertx.core.http.RequestOptions;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import tech.powerjob.common.PowerSerializable;
|
||||
import tech.powerjob.remote.framework.base.RemotingException;
|
||||
import tech.powerjob.remote.framework.base.URL;
|
||||
import tech.powerjob.remote.framework.transporter.Protocol;
|
||||
import tech.powerjob.remote.framework.transporter.Transporter;
|
||||
import tech.powerjob.remote.http.HttpProtocol;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
/**
|
||||
* VertxTransporter
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/1/1
|
||||
*/
|
||||
@Slf4j
|
||||
public class VertxTransporter implements Transporter {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private static final Protocol PROTOCOL = new HttpProtocol();
|
||||
|
||||
public VertxTransporter(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol() {
|
||||
return PROTOCOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tell(URL url, PowerSerializable request) {
|
||||
post(url, request, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> CompletionStage<T> ask(URL url, PowerSerializable request, Class<T> clz) throws RemotingException {
|
||||
return post(url, request, clz);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> CompletionStage<T> post(URL url, PowerSerializable request, Class<T> clz) {
|
||||
final String host = url.getAddress().getHost();
|
||||
final int port = url.getAddress().getPort();
|
||||
final String path = url.getLocation().toPath();
|
||||
RequestOptions requestOptions = new RequestOptions()
|
||||
.setMethod(HttpMethod.POST)
|
||||
.setHost(host)
|
||||
.setPort(port)
|
||||
.setURI(path);
|
||||
// 获取远程服务器的HTTP连接
|
||||
Future<HttpClientRequest> httpClientRequestFuture = httpClient.request(requestOptions);
|
||||
// 转换 -> 发送请求获取响应
|
||||
Future<HttpClientResponse> responseFuture = httpClientRequestFuture.compose(httpClientRequest ->
|
||||
httpClientRequest
|
||||
.putHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
|
||||
.send(JsonObject.mapFrom(request).toBuffer())
|
||||
);
|
||||
return responseFuture.compose(httpClientResponse -> {
|
||||
// throw exception
|
||||
final int statusCode = httpClientResponse.statusCode();
|
||||
if (statusCode != HttpResponseStatus.OK.code()) {
|
||||
// CompletableFuture.get() 时会传递抛出该异常
|
||||
throw new RemotingException(String.format("request [host:%s,port:%s,url:%s] failed, status: %d, msg: %s",
|
||||
host, port, path, statusCode, httpClientResponse.statusMessage()
|
||||
));
|
||||
}
|
||||
|
||||
return httpClientResponse.body().compose(x -> {
|
||||
|
||||
if (clz == null) {
|
||||
return Future.succeededFuture(null);
|
||||
}
|
||||
|
||||
if (clz.equals(String.class)) {
|
||||
return Future.succeededFuture((T) x.toString());
|
||||
}
|
||||
|
||||
return Future.succeededFuture(x.toJsonObject().mapTo(clz));
|
||||
});
|
||||
})
|
||||
.onFailure(t -> log.warn("[VertxTransporter] post to url[{}] failed,msg: {}", url, ExceptionUtils.getMessage(t)))
|
||||
.toCompletionStage();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package tech.powerjob.remote.http;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tech.powerjob.common.enums.Protocol;
|
||||
import tech.powerjob.common.utils.CommonUtils;
|
||||
import tech.powerjob.remote.framework.BenchmarkActor;
|
||||
import tech.powerjob.remote.framework.base.Address;
|
||||
import tech.powerjob.remote.framework.base.HandlerLocation;
|
||||
import tech.powerjob.remote.framework.base.URL;
|
||||
import tech.powerjob.remote.framework.engine.EngineConfig;
|
||||
import tech.powerjob.remote.framework.engine.EngineOutput;
|
||||
import tech.powerjob.remote.framework.engine.RemoteEngine;
|
||||
import tech.powerjob.remote.framework.engine.impl.PowerJobRemoteEngine;
|
||||
import tech.powerjob.remote.framework.transporter.Transporter;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* HttpVertxCSInitializerTest
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/1/2
|
||||
*/
|
||||
@Slf4j
|
||||
class HttpVertxCSInitializerTest {
|
||||
|
||||
@Test
|
||||
void testHttpVertxCSInitializerTest() throws Exception {
|
||||
|
||||
final Address address = new Address().setPort(7890).setHost("127.0.0.1");
|
||||
|
||||
EngineConfig engineConfig = new EngineConfig()
|
||||
.setType(Protocol.HTTP.name())
|
||||
.setBindAddress(address)
|
||||
.setActorList(Lists.newArrayList(new BenchmarkActor()));
|
||||
|
||||
RemoteEngine engine = new PowerJobRemoteEngine();
|
||||
EngineOutput engineOutput = engine.start(engineConfig);
|
||||
log.info("[HttpVertxCSInitializerTest] engine start up successfully!");
|
||||
Transporter transporter = engineOutput.getTransporter();
|
||||
|
||||
BenchmarkActor.BenchmarkRequest request = new BenchmarkActor.BenchmarkRequest()
|
||||
.setContent("request from test")
|
||||
.setBlockingMills(100)
|
||||
.setResponseSize(10240);
|
||||
|
||||
log.info("[HttpVertxCSInitializerTest] test empty request!");
|
||||
URL emptyURL = new URL()
|
||||
.setAddress(address)
|
||||
.setLocation(new HandlerLocation().setMethodPath("emptyReturn").setRootPath("benchmark"));
|
||||
transporter.tell(emptyURL, request);
|
||||
|
||||
log.info("[HttpVertxCSInitializerTest] test string request!");
|
||||
URL stringURL = new URL()
|
||||
.setAddress(address)
|
||||
.setLocation(new HandlerLocation().setMethodPath("stringReturn").setRootPath("benchmark"));
|
||||
final String strResponse = transporter.ask(stringURL, request, String.class).toCompletableFuture().get();
|
||||
log.info("[HttpVertxCSInitializerTest] strResponse: {}", strResponse);
|
||||
|
||||
log.info("[HttpVertxCSInitializerTest] test normal request!");
|
||||
URL url = new URL()
|
||||
.setAddress(address)
|
||||
.setLocation(new HandlerLocation().setMethodPath("standard").setRootPath("benchmark"));
|
||||
|
||||
final CompletionStage<BenchmarkActor.BenchmarkResponse> benchmarkResponseCompletionStage = transporter.ask(url, request, BenchmarkActor.BenchmarkResponse.class);
|
||||
final BenchmarkActor.BenchmarkResponse response = benchmarkResponseCompletionStage.toCompletableFuture().get(10, TimeUnit.SECONDS);
|
||||
log.info("[HttpVertxCSInitializerTest] response: {}", response);
|
||||
|
||||
|
||||
|
||||
|
||||
CommonUtils.easySleep(10000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user