You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Inversify Express服务无法与Socket.IO WebSocket服务器建立连接

问题:Inversify+Express服务器正常运行,但Socket.IO WebSocket连接失败

基于Inversify搭建的Express服务器能够正常启动,但尝试建立Socket.IO WebSocket连接时失败,连接地址为ws://127.0.0.1:3008/web-socket-service/api/v1/socket.io?EIO=3&transport=websocket,出现断开连接错误。

相关代码

主启动文件

import 'module-alias/register';
import 'reflect-metadata';
import sourceMapSupport from 'source-map-support';
import { LIB_ENV_CONFIG, logger } from '@sn/shared';
import { AppServer, IExpressMiddleware, InversifyFactory } from '@sn/server';
import { AsyncContainerModule } from 'inversify';
import { customMiddleware } from './shared/middleware/AppMiddleware';
import swaggerMiddleware from './shared/middleware/Swagger';
import { AppModule } from '@ioc/IocConfig';
import { Server, Socket } from 'socket.io';
import * as http from 'http';

sourceMapSupport.install();

const expressApp = (async (): Promise<void> => {
  try {
    logger.pushContext(LIB_ENV_CONFIG.LIB_SERVICE_NAME);
    const serviceGlobalCustomMiddlewares: IExpressMiddleware[] = [
      customMiddleware,
      swaggerMiddleware
    ];
    const containers: AsyncContainerModule[] = await InversifyFactory.create(AppModule);
    const app = new AppServer(containers, serviceGlobalCustomMiddlewares);
    await app.startServer();

    const expressServerInstance = app.getServer();
    const httpServer = http.createServer(expressServerInstance);
    const io: Server = new Server(httpServer);

    io.on('connection', (socket: Socket) => {
      console.log('a user connected');

      socket.on('disconnect', () => {
        console.log('user disconnected');
      });

      socket.on('chat message', (msg: string) => {
        io.emit('chat message', msg);
      });
    });
    io.on('listening', () => {
      console.log('WebSocket server is listening for connections');
    });
  } catch (err) {
    logger.error(`Server setup error in apps/${LIB_ENV_CONFIG.LIB_SERVICE_NAME}`, err);
  }
})();

expressApp;

Inversify AppServer实现

import 'reflect-metadata';
/* eslint-disable  @typescript-eslint/no-explicit-any */
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('source-map-support').install();
import * as dotenv from 'dotenv';
import { InversifyExpressServer } from 'inversify-express-utils';
import { ExpressMiddleware, errorMiddleware } from './../middleware';
import { LIB_ENV_CONFIG, logger } from '@sn/shared';
import { AsyncContainerModule, Container } from 'inversify';
import { IExpressMiddleware } from './../interface/IMiddleware';
import { HealthCheckRouter } from './HealthCheckRouter';

dotenv.config();

export class AppServer {
  private readonly servicePort = LIB_ENV_CONFIG.LIB_PORT;
  private readonly serviceName = LIB_ENV_CONFIG.LIB_SERVICE_NAME;
  private readonly serviceUrl = LIB_ENV_CONFIG.LIB_SERVICE_URL;
  private readonly serviceApiVersionUri = LIB_ENV_CONFIG.LIB_CLIENT_API_VERSION;
  private readonly serviceBaseUrl = LIB_ENV_CONFIG.LIB_SERVICE_BASE_URL;
  private readonly serviceEnv = LIB_ENV_CONFIG.LIB_NODE_ENV || 'development';
  private rootPath = this.serviceBaseUrl + this.serviceApiVersionUri;

  private server: InversifyExpressServer;
  private serverInstance;
  constructor(
    serviceInversifyContainers: AsyncContainerModule[],
    serviceGlobalCustomMiddlewares: IExpressMiddleware[]
  ) {
    this.loadInversifyServer(serviceInversifyContainers);
    this.loadLibMiddlewares(serviceGlobalCustomMiddlewares);
    this.loadErrorMiddleware();
  }

  private loadInversifyServer = (serviceInversifyContainers: AsyncContainerModule[]) => {
    const container = new Container();

    HealthCheckRouter;
    serviceInversifyContainers.forEach((moduleConfigContainer: any) => {
      container.loadAsync(moduleConfigContainer);
    });

    this.server = new InversifyExpressServer(container, null, {
      rootPath: this.rootPath
    });
  };

  private loadLibMiddlewares = (serviceGlobalCustomMiddlewares: IExpressMiddleware[]) => {
    this.server.setConfig((app) => {
      logger.info(`LOADING LIBRARY LEVEL MIDDLEWARES ...........`);
      new ExpressMiddleware(app);
      serviceGlobalCustomMiddlewares.forEach((serviceMiddleware) => {
        serviceMiddleware(app);
      });
    });
  };

  private loadErrorMiddleware = () => {
    this.server.setErrorConfig(errorMiddleware);
  };

  startServer = async () => {
    const serverInstance = this.server.build();
    this.serverInstance = serverInstance;
    serverInstance.listen(this.servicePort, () => {
      logger.info(
        `FOR SERVICE: [ ${this.serviceName} ] -> Server running at ${this.serviceUrl}:${this.servicePort}${this.rootPath} -> on ${this.serviceEnv} environment`
      );
    });
  };

  getServer = () => {
    return this.serverInstance;
  };
}

问题原因分析

  1. 端口监听未正确绑定:AppServer.startServer()已经让Express实例单独监听了目标端口,后续创建的httpServer虽绑定了Express实例,但未调用listen()启动监听,导致Socket.IO没有实际提供服务。
  2. Socket.IO路径不匹配:InversifyExpressServer设置了rootPath前缀(如/web-socket-service/api/v1),但Socket.IO默认路径为/socket.io,客户端连接时将前缀加到Socket.IO路径前,导致服务端无法识别该路径。

解决方案

步骤1:修改AppServer,取消内部端口监听

调整AppServer.startServer方法,不再直接启动端口监听,仅返回构建好的Express实例:

// 修改AppServer的startServer方法
startServer = async () => {
  const serverInstance = this.server.build();
  this.serverInstance = serverInstance;
  logger.info(
    `FOR SERVICE: [ ${this.serviceName} ] -> Server ready to run at ${this.serviceUrl}:${this.servicePort}${this.rootPath} -> on ${this.serviceEnv} environment`
  );
  return serverInstance;
};

步骤2:主启动文件统一管理httpServer与Socket.IO

在主代码中创建httpServer托管Express实例,启动端口监听,并配置Socket.IO路径匹配服务前缀:

const expressApp = (async (): Promise<void> => {
  try {
    logger.pushContext(LIB_ENV_CONFIG.LIB_SERVICE_NAME);
    const serviceGlobalCustomMiddlewares: IExpressMiddleware[] = [
      customMiddleware,
      swaggerMiddleware
    ];
    const containers: AsyncContainerModule[] = await InversifyFactory.create(AppModule);
    const app = new AppServer(containers, serviceGlobalCustomMiddlewares);
    const expressServerInstance = await app.startServer();

    // 创建httpServer并绑定Express实例
    const httpServer = http.createServer(expressServerInstance);
    // 配置Socket.IO路径,匹配服务的rootPath前缀
    const io: Server = new Server(httpServer, {
      path: `${LIB_ENV_CONFIG.LIB_SERVICE_BASE_URL}${LIB_ENV_CONFIG.LIB_CLIENT_API_VERSION}/socket.io`
    });

    io.on('connection', (socket: Socket) => {
      console.log('a user connected');

      socket.on('disconnect', () => {
        console.log('user disconnected');
      });

      socket.on('chat message', (msg: string) => {
        io.emit('chat message', msg);
      });
    });

    // 启动httpServer监听端口
    httpServer.listen(LIB_ENV_CONFIG.LIB_PORT, () => {
      console.log(`WebSocket server is listening on port ${LIB_ENV_CONFIG.LIB_PORT}`);
    });
  } catch (err) {
    logger.error(`Server setup error in apps/${LIB_ENV_CONFIG.LIB_SERVICE_NAME}`, err);
  }
})();

expressApp;

步骤3:客户端连接路径同步调整

确保客户端连接时使用与服务端一致的路径:

// 客户端代码示例
import { io } from 'socket.io-client';

const socket = io('http://127.0.0.1:3008', {
  path: '/web-socket-service/api/v1/socket.io'
});

调整后,Express与Socket.IO共用同一个httpServer端口,路径匹配一致,WebSocket连接即可正常建立。

内容的提问来源于stack exchange,提问作者Mr X

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 19:20:56