rebue.wheel.vertx.verticle.AbstractWebVerticle Maven / Gradle / Ivy
The newest version!
package rebue.wheel.vertx.verticle;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import com.github.f4b6a3.ulid.UlidCreator;
import com.google.inject.Injector;
import io.netty.handler.codec.compression.StandardCompressionOptions;
import io.vertx.amqp.AmqpClient;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.Message;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.impl.Arguments;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.SelfSignedCertificate;
import io.vertx.core.streams.ReadStream;
import io.vertx.core.streams.StreamBase;
import io.vertx.core.streams.WriteStream;
import io.vertx.ext.web.AllowForwardHeaders;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.*;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.kafka.client.common.KafkaClientOptions;
import io.vertx.kafka.client.consumer.KafkaConsumer;
import io.vertx.kafka.client.producer.KafkaProducer;
import io.vertx.kafka.client.producer.KafkaProducerRecord;
import io.vertx.redis.client.RedisAPI;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import rebue.wheel.api.cst.SsmCst;
import rebue.wheel.api.ssm.StringSsm;
import rebue.wheel.vertx.CtxCst;
import rebue.wheel.vertx.config.WebProperties;
import rebue.wheel.vertx.guice.InjectorVerticle;
import rebue.wheel.vertx.spi.*;
import rebue.wheel.vertx.web.BlackListHandler;
import rebue.wheel.vertx.web.CompressResponseHandler;
import rebue.wheel.vertx.web.LimitRateHandler;
import rebue.wheel.vertx.web.PrintSrcIpHandler;
@Slf4j
public abstract class AbstractWebVerticle extends AbstractVerticle implements InjectorVerticle {
protected HttpServer httpServer;
private HttpServer http2httpsServer;
@Inject
@Named("mainId")
private String mainId;
@Setter
protected Injector injector;
protected WebProperties webProperties;
protected Router router;
private MessageConsumer startConsumer;
@Override
public void start(Promise startPromise) {
log.info("WebVerticle start deployed");
webProperties = config().mapTo(WebProperties.class);
Map httpServerConfig = webProperties.getServer();
final HttpServerOptions httpServerOptions = httpServerConfig == null ? new HttpServerOptions()
: new HttpServerOptions(JsonObject.mapFrom(httpServerConfig));
log.info("创建路由");
router = Router.router(this.vertx);
log.info("通过SPI加载web插件工厂");
ServiceLoader webPluginFactoryServiceLoader = ServiceLoader.load(VertxWebPluginFactory.class);
log.info("初始化web插件工厂");
webPluginFactoryServiceLoader.forEach(factory -> {
log.info("初始化web插件工厂: {}", factory.name());
factory.init(vertx, router, injector, webProperties.getGlobalRouteHandlers().get(factory.name()));
});
AllowForwardHeaders allowForwardHeaders = AllowForwardHeaders.valueOf(webProperties.getAllowForward());
log.info("设置allow forward: {}", allowForwardHeaders);
router.allowForward(allowForwardHeaders);
// 全局route
final Route globalRoute = router.route();
// 支持压缩与解压缩算法
Object compressors = httpServerConfig == null ? null : httpServerConfig.get("compressors");
if (compressors != null) {
log.info("开启压缩与解压缩");
httpServerOptions.setCompressionSupported(true);
httpServerOptions.setDecompressionSupported(true);
@SuppressWarnings("unchecked")
List list = (List) compressors;
for (String compressor : list) {
log.info("add compressor: {}", compressor);
switch (compressor) {
case "brotli" -> httpServerOptions.addCompressor(StandardCompressionOptions.brotli());
case "deflate" -> httpServerOptions.addCompressor(StandardCompressionOptions.deflate());
case "gzip" -> httpServerOptions.addCompressor(StandardCompressionOptions.gzip());
case "snappy" -> httpServerOptions.addCompressor(StandardCompressionOptions.snappy());
case "zstd" -> httpServerOptions.addCompressor(StandardCompressionOptions.zstd());
}
}
globalRoute.handler(new CompressResponseHandler());
}
// 全局返回响应时间(写入x-response-time到响应头)
if (webProperties.getReturnResponseTime()) {
log.info("开启返回响应时间");
globalRoute.handler(ResponseTimeHandler.create());
}
// 超时时间
final Long timeout = webProperties.getTimeout();
if (timeout != null && timeout != 0) {
final Integer timeoutErrorCode = webProperties.getTimeoutErrorCode();
final int errorCode = timeoutErrorCode != null ? timeoutErrorCode : 503;
log.info("开启超时{}毫秒未响应返回错误状态码{}", timeout, errorCode);
globalRoute.handler(TimeoutHandler.create(timeout, errorCode));
}
// 记录日志
if (webProperties.getIsLogging()) {
log.info("开启日志记录");
globalRoute.handler(LoggerHandler.create(webProperties.getLoggerFormat()));
}
// 是否打印来源的IP
if (webProperties.getPrintSrcIp()) {
log.info("开启打印来源的IP");
globalRoute.handler(new PrintSrcIpHandler());
}
// 自动响应内容类型(处理器会通过 getAcceptableContentType 方法来选择适当的内容类型)
if (webProperties.getIsAutoResponseContentType()) {
log.info("开启自动响应内容类型");
globalRoute.handler(ResponseContentTypeHandler.create());
}
// 是否开启黑名单
if (webProperties.getBlackList().getEnabled()) {
log.info("开启黑名单");
RedisAPI redisClient = injector.getInstance(RedisAPI.class);
globalRoute.handler(new BlackListHandler(webProperties.getBlackList(), redisClient));
}
// 是否限流
if (webProperties.getLimitRate().getEnabled()) {
log.info("开启限流");
RedisAPI redisClient = injector.getInstance(RedisAPI.class);
globalRoute.handler(new LimitRateHandler(webProperties.getLimitRate(), webProperties.getBlackList(), redisClient));
}
// CORS
if (webProperties.getIsCors()) {
log.info("开启CORS");
globalRoute.handler(CorsHandler.create());
}
// 配置websocket路由
if (!webProperties.getWebsocketRoutes().isEmpty()) {
// noinspection unchecked
KafkaProducer kafkaProducer = injector.getInstance(KafkaProducer.class);
KafkaClientOptions kafkaClientOptions = injector.getInstance(KafkaClientOptions.class);
// 独立的组来使用广播模式
kafkaClientOptions.setConfig("group.id", UlidCreator.getUlid().toLowerCase());
for (WebProperties.SseRouteProperties sseRoute : webProperties.getWebsocketRoutes()) {
log.info("配置WebSocket路由: {}", sseRoute.getPath());
router.route(sseRoute.getPath())
.handler(routingContext -> routingContext.request().toWebSocket()
.onSuccess(socket -> this.handleSocket(socket,
sseRoute.getSendTopic(), sseRoute.getReceiveTopic(),
sseRoute.getUserAgentIdCookieKey(),
kafkaProducer, KafkaConsumer.create(vertx, kafkaClientOptions)))
.onFailure(err -> log.error("websocket处理发生异常", err)));
}
}
// 配置sockjs路由
if (!webProperties.getSockjsRoutes().isEmpty()) {
SockJSHandler sockjsHandler = injector.getInstance(SockJSHandler.class);
// SockJSBridgeOptions sockjsBridgeOptions = injector.getInstance(SockJSBridgeOptions.class);
// noinspection unchecked
KafkaProducer kafkaProducer = injector.getInstance(KafkaProducer.class);
KafkaClientOptions kafkaClientOptions = injector.getInstance(KafkaClientOptions.class);
// 独立的组来使用广播模式
kafkaClientOptions.setConfig("group.id", UlidCreator.getUlid().toLowerCase());
for (WebProperties.SseRouteProperties sseRoute : webProperties.getSockjsRoutes()) {
log.info("配置SockJS路由: {}", sseRoute.getPath());
router.route(sseRoute.getPath() + "*")
.handler(BodyHandler.create())
// .subRouter(sockjsHandler.bridge(sockjsBridgeOptions,
// bridgeEvent -> this.handleSocket(bridgeEvent.socket(),
.subRouter(sockjsHandler.socketHandler(socket -> this.handleSocket(socket,
sseRoute.getSendTopic(), sseRoute.getReceiveTopic(),
sseRoute.getUserAgentIdCookieKey(),
kafkaProducer, KafkaConsumer.create(vertx, kafkaClientOptions))));
}
}
log.info("添加全局路由处理器");
addGlobalRouteHandler();
log.info("通过SPI加载全局路由处理器");
ServiceLoader globalRouteHandlerServiceLoader = ServiceLoader.load(GlobalRouteHandlerFactory.class);
log.info("添加全局路由前置处理器");
globalRouteHandlerServiceLoader.forEach(factory -> {
Handler preHandler = factory.createPreHandler();
if (preHandler != null) {
log.info("添加全局路由前置处理器: {}", factory.name());
globalRoute.handler(preHandler);
}
});
log.info("通过SPI加载路由前置配置器");
ServiceLoader routePreConfiguratorServiceLoader = ServiceLoader.load(RoutePreConfigurator.class);
log.info("前置配置路由");
routePreConfiguratorServiceLoader.forEach(factory -> factory.config(router));
log.info("配置路由器");
configRouter();
log.info("通过SPI加载路由后置配置器");
ServiceLoader routePostConfiguratorServiceLoader = ServiceLoader.load(RoutePostConfigurator.class);
log.info("后置配置路由");
routePostConfiguratorServiceLoader.forEach(factory -> factory.config(router));
log.info("添加全局路由后置处理器");
globalRouteHandlerServiceLoader.forEach(factory -> {
Handler postHandler = factory.createPostHandler();
if (postHandler != null) {
log.info("添加全局路由后置处理器: {}", factory.name());
globalRoute.handler(postHandler);
}
});
log.info("添加全局路由错误处理");
globalRoute.failureHandler(ErrorHandler.create(this.vertx));
// 是否实现自签名证书
if (webProperties.getSelfSignedCertificate()) {
log.info("实现自签名证书");
SelfSignedCertificate certificate = SelfSignedCertificate.create();
httpServerOptions
.setSsl(true)
.setKeyCertOptions(certificate.keyCertOptions())
.setTrustOptions(certificate.trustOptions());
}
vertx.getOrCreateContext().put(CtxCst.HTTP_SERVER_SSL, httpServerOptions.isSsl());
this.httpServer = this.vertx.createHttpServer(httpServerOptions).requestHandler(router);
Map http2https = webProperties.getHttp2https();
if (http2https != null) {
final HttpServerOptions http2httpsServerOptions = new HttpServerOptions(
JsonObject.mapFrom(webProperties.getHttp2https()));
int http2httpsPort = http2httpsServerOptions.getPort();
int httpsPort = httpServerOptions.getPort();
Arguments.require(http2httpsPort != 0, "web.config.http2https.port不能为null或0");
Arguments.require(httpsPort != 0, "web.config.server.port不能为null或0");
this.http2httpsServer = this.vertx.createHttpServer(http2httpsServerOptions)
.requestHandler(req -> req.response()
.setStatusCode(301)
.putHeader("Location", req.absoluteURI()
.replace("http", "https")
.replace(":" + http2httpsPort, ":" + httpsPort))
.end());
}
configHttpServer(httpServer);
log.info("通过SPI加载Http服务器配置器");
ServiceLoader httpServerConfiguratorServiceLoader = ServiceLoader.load(HttpServerConfigurator.class);
log.info("配置Http服务器");
httpServerConfiguratorServiceLoader.forEach(factory -> factory.config(httpServer));
final String address = AbstractMainVerticle.EVENT_BUS_DEPLOY_SUCCESS + "::" + this.mainId;
log.info("WebVerticle注册消费EventBus事件-MainVerticle部署成功事件: {}", address);
this.startConsumer = this.vertx.eventBus().consumer(address, this::handleStart);
// 注册完成处理器
this.startConsumer.completionHandler(res -> {
log.info("WebVerticle end deployed");
if (res.succeeded()) {
log.info("WebVerticle deployed success");
startPromise.complete();
} else {
log.error("WebVerticle deployed fail", res.cause());
startPromise.fail(res.cause());
}
});
}
/**
* 添加全局路由处理器
*/
protected void addGlobalRouteHandler() {
log.info("未重写addGlobalRouteHandler方法");
}
@Override
public void stop() {
log.info("WebVerticle stop");
if (http2httpsServer != null)
http2httpsServer.close();
this.httpServer.close();
}
/**
* 配置路由
*/
protected void configRouter() {
log.info("配置路由前清空之前配置的路由,因为可能是刷新路由配置触发");
if (router != null && router.getRoutes() != null) {
router.getRoutes().clear();
}
}
/**
* @param httpServer 配置http服务器
*/
protected void configHttpServer(HttpServer httpServer) {
log.info("AbstractWebVerticle的子类未继承实现configHttpServer方法: {}", httpServer.actualPort());
}
private void handleStart(final Message message) {
log.info("WebVerticle start");
this.startConsumer.unregister(result -> {
this.httpServer.listen(res -> {
if (res.succeeded()) {
int port = res.result().actualPort();
vertx.getOrCreateContext().put(CtxCst.HTTP_SERVER_PORT, port);
log.info("HTTP server started on port {}", port);
} else {
log.error("HTTP server start fail", res.cause());
}
// 是否开启动态路由
if (webProperties.getDynamicRoute().getEnabled()) {
log.info("开启动态路由");
switch (webProperties.getDynamicRoute().getMqType().toLowerCase()) {
case "rabbitmq" -> {
AmqpClient amqpClient = injector.getInstance(AmqpClient.class);
amqpClient.createReceiver(webProperties.getDynamicRoute().getMqName())
.compose(receiver -> {
log.info("订阅动态路由刷新队列成功-rabbitmq");
// 刷新动态路由
configRouter();
return Future.succeededFuture();
}).recover(err -> {
log.error("订阅动态路由刷新队列失败", err);
return Future.failedFuture(err);
});
}
case "kafka" -> {
// noinspection unchecked
KafkaConsumer kafkaConsumer = injector.getInstance(KafkaConsumer.class);
kafkaConsumer.subscribe(webProperties.getDynamicRoute().getMqName())
.compose(v -> {
log.info("订阅动态路由刷新队列成功-kafka");
// 刷新动态路由
configRouter();
return Future.succeededFuture();
}).recover(err -> {
log.error("订阅动态路由刷新队列失败", err);
return Future.failedFuture(err);
});
}
}
}
});
if (http2httpsServer != null)
http2httpsServer.listen(res -> {
if (res.succeeded()) {
log.info("HTTP to HTTPS server started on port {}", res.result().actualPort());
} else {
log.error("HTTP to HTTPS server start fail", res.cause());
}
});
});
}
/**
* 处理socket
*
* @param socketStream socket流
*/
private void handleSocket(StreamBase socketStream,
String sendTopic, String receiveTopic, String userAgentIdCookieKey,
KafkaProducer kafkaProducer,
KafkaConsumer kafkaConsumer) {
log.info("接收到新的浏览器连接");
// noinspection unchecked
ReadStream readStream = (ReadStream) socketStream;
// noinspection unchecked
WriteStream writeStream = (WriteStream) socketStream;
String userAgentId = UlidCreator.getUlid().toLowerCase();
kafkaConsumer.handler(record -> {
Buffer buffer = record.record().value();
log.debug("接收到发送浏览器事件({}): {}", userAgentId, buffer);
writeStream.write(buffer);
});
readStream.handler(buffer -> {
log.debug("接收到浏览器发送过来的消息: {}-{}", userAgentId, buffer);
kafkaProducer.write(KafkaProducerRecord.create(receiveTopic, buffer));
}).exceptionHandler(err -> log.error("socket处理发生异常", err)
// 断开连接
).endHandler(v -> {
log.info("浏览器断开连接: {}", userAgentId);
kafkaConsumer.unsubscribe().onComplete(vv -> kafkaConsumer.close());
});
JsonObject data = new JsonObject()
.put("userAgentIdCookieKey", userAgentIdCookieKey)
.put("userAgentId", userAgentId);
writeStream.write(JsonObject.mapFrom(StringSsm.of(SsmCst.USER_AGENT_ID, data)).toBuffer())
.onSuccess(v -> {
log.info("订阅发送浏览器事件: {}", userAgentId);
kafkaConsumer.subscribe(sendTopic)
.onSuccess(vv -> log.info("订阅发送浏览器事件成功: {}", userAgentId))
.onFailure(err -> log.error("订阅发送浏览器事件失败: {}", userAgentId, err));
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy