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; }; }
问题原因分析
- 端口监听未正确绑定:
AppServer.startServer()已经让Express实例单独监听了目标端口,后续创建的httpServer虽绑定了Express实例,但未调用listen()启动监听,导致Socket.IO没有实际提供服务。 - 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
相关产品推荐
相关产品推荐

