All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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