YunaiV/ruoyi-vue-pro · critical · RuntimeException

HTTP Hook 服务启动失败

Error message

HTTP Hook 服务启动失败

What it means

Thrown when the Vert.x HTTP server hosting the EMQX HTTP Hook endpoints (/mqtt/auth, /mqtt/acl, /mqtt/event) fails to start. The code calls httpServer.listen() with a 10-second join (.get(10, TimeUnit.SECONDS)); any bind failure, SSL cert/key load error, or listen timeout is wrapped and rethrown as RuntimeException. Because IotEmqxProtocol.start() calls this first and propagates, it aborts the whole protocol instance startup.

Solutions

  1. Check the port is free: ss -ltnp | grep <port> or lsof -i:<port>, then free it or change properties.port.
  2. If SSL is enabled, verify emqx.http.ssl-cert-path and emqx.http.ssl-key-path exist and are readable by the gateway process, and form a valid key/cert pair (openssl x509 -noout -modulus matches key).
  3. Ensure only one gateway instance binds the port (no duplicate protocol instances with the same port).
  4. Raise the 10s listen timeout or check Vert.x event-loop health if bind is slow under load.
Defensive patterns

Strategy: validation

Validate before calling

// before start(), confirm the hook port is free and SSL files (if enabled) exist
try (ServerSocket probe = new ServerSocket(properties.getPort())) {
    // port is free
} catch (IOException e) {
    throw new IllegalStateException("HTTP Hook 端口被占用: " + properties.getPort(), e);
}
if (Boolean.TRUE.equals(httpConfig.getSslEnabled())) {
    java.nio.file.Files.readString(java.nio.file.Path.of(httpConfig.getSslCertPath()));
    java.nio.file.Files.readString(java.nio.file.Path.of(httpConfig.getSslKeyPath()));
}

Try / catch

// in IotProtocolManager.start(), isolate each protocol so one bind failure doesn't abort all
for (ProtocolProperties config : protocolConfigs) {
    try {
        IotProtocol protocol = createProtocol(config);
        if (protocol != null) { protocol.start(); protocols.add(protocol); }
    } catch (Exception e) {
        log.error("[start][协议实例 {} 启动失败,跳过]", config.getId(), e);
    }
}

Prevention

When it happens

Trigger: properties.getPort() is already bound by another process; SSL enabled (emqx.http.ssl-enabled=true) but ssl-cert-path / ssl-key-path is missing, unreadable, or invalid PEM; the OS denies the bind (privileged port <1024 without permission); listen() does not complete within 10 seconds.

Common situations: Port conflict with another EMQX protocol instance or service; running two gateway processes; SSL cert path typo or wrong working directory; cert/key file permissions; EMQX configured to call back on a port the gateway never opened.

Related errors


AI-assisted analysis of YunaiV/ruoyi-vue-pro@0418084e22 (2026-08-14). Data as JSON: /api/errors/b3c199a11557a2d9. Report an issue: GitHub.

Appendix: source

Thrown at yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxProtocol.java:229

        if (httpConfig != null && Boolean.TRUE.equals(httpConfig.getSslEnabled())) {
            Assert.notBlank(httpConfig.getSslCertPath(), "EMQX HTTP SSL 证书路径(emqx.http.ssl-cert-path)不能为空");
            Assert.notBlank(httpConfig.getSslKeyPath(), "EMQX HTTP SSL 私钥路径(emqx.http.ssl-key-path)不能为空");
            PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
                    .setKeyPath(httpConfig.getSslKeyPath())
                    .setCertPath(httpConfig.getSslCertPath());
            options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
        }
        try {
            httpServer = vertx.createHttpServer(options)
                    .requestHandler(router)
                    .listen()
                    .toCompletionStage().toCompletableFuture()
                    .get(10, TimeUnit.SECONDS);
            log.info("[startHttpServer][IoT EMQX 协议 {} HTTP Hook 服务启动成功, port: {}, ssl: {}]",
                    getId(), properties.getPort(), httpConfig != null && Boolean.TRUE.equals(httpConfig.getSslEnabled()));
        } catch (Exception e) {
            log.error("[startHttpServer][IoT EMQX 协议 {} HTTP Hook 服务启动失败, port: {}]", getId(), properties.getPort(), e);
            throw new RuntimeException("HTTP Hook 服务启动失败", e);
        }
    }

    private void stopHttpServer() {
        if (httpServer == null) {
            return;
        }
        try {
            httpServer.close().toCompletionStage().toCompletableFuture()
                    .get(5, TimeUnit.SECONDS);
            log.info("[stopHttpServer][IoT EMQX 协议 {} HTTP Hook 服务已停止]", getId());
        } catch (Exception e) {
            log.error("[stopHttpServer][IoT EMQX 协议 {} HTTP Hook 服务停止失败]", getId(), e);
        } finally {
            httpServer = null;
        }
    }

View on GitHub (pinned to 0418084e22)