为 Express 定义 Websocket 路由

Posted

技术标签:

【中文标题】为 Express 定义 Websocket 路由【英文标题】:Defining Websocket routes for Express 【发布时间】:2021-10-29 18:23:58 【问题描述】:

如何在 ExpressJS 应用程序中使用 ws 库为 Websocket 定义路由?并行设置两层非常容易,但是 Websocket 层将无法从 ExpressJS 中间件(例如身份验证)中受益。我能找到的唯一实现是express-ws,由于不是最新的,它存在严重的错误,并且严重依赖猴子补丁才能工作。

【问题讨论】:

【参考方案1】:

部分修改自此answer。修改您的入口文件以包含以下内容:

/* index.ts */
import http from 'http';
import express from 'express';
import exampleRouter from './exampleRouter';

// set up express and create a http server listen for websocket requests on the same port
const app = express();
const server = http.createServer(app);

// listen for websocket requests, which are simple HTTP GET requests with an upgrade header
// NOTE: this must occur BEFORE other middleware is set up if you want the additional ws.handled functionality to close unhandled requests
server.on('upgrade', (req: Request &  ws:  socket: Socket, head: Buffer, handled: Boolean  , socket: Socket, head: Buffer) => 
  // create a dummy response to pass the request into express
  const res = new http.ServerResponse(req);
  // assign socket and head to a new field in the request object
  // optional **handled** field lets us know if there a route processed the websocket request, else we terminate it later on
  req.ws =  socket, head, handled: false ;
  // have Express process the request
  app(req, res);
);

/* whatever Express middlewares you want here, such as authentication */
app.use('/example', exampleRouter);

// set up a middleware to destroy unhandled websocket requests and returns a 403
// NOTE: this must occur AFTER your other middlewares but BEFORE the server starts listening for requests
app.use((req: Request &  ws?:  socket: Socket, head: Buffer, handled: Boolean  , res: Response, next: NextFunction): void => 
  if (req.ws && req.ws.handled === false) 
    req.ws.socket.destroy();
    res.status(404).json('404: Websocket route not found');
  
  next();
);

const port = process.env.PORT || 8080;
server.listen(port);

具有 ws 功能的 Express Router 示例,但可以提取逻辑以用于一次性

/* RouterWithWebSockets.ts */
// this is just a simple abstraction implementation so the you can set up multiple ws routes with the same router
// without having to rewrite the WSS code or monkeypatch the function into the Express Router directly
import express from 'express';
import  WebSocketServer, WebSocket  from 'ws';

class RouterWithWebSockets 
  router;

  constructor(router = express.Router()) 
    this.router = router;
  

  ws = (path: string, callback: (ws: WebSocket) => void, ...middleware: any): void => 
    // set up a new WSS with the provided path/route to handle websockets
    const wss = new WebSocketServer(
      noServer: true,
      path,
    );

    this.router.get(path, ...middleware, (req: any, res, next) => 
      // just an extra check to deny upgrade requests if the path/route does not match
      // you can process this route as a regular HTTP GET request if it's not a websocket upgrade request by replacing the next()
      if (!req.headers.upgrade || path !== req.url) 
        next();
       else 
        req.ws.handled = true;
        wss.handleUpgrade(req, req.ws.socket, req.ws.head, (ws: WebSocket) => 
          callback(ws);
        );
      
    );
  ;


export default RouterWithWebSockets;

最后,这是一个带有 Websocket 路由的示例路由器

/* exampleRouter.ts */

const routerWithWebSockets = new RouterWithWebSockets();

routerWithWebSockets.router.get('/nonWSRoute', doSomething1); // processed as HTTP GET request

routerWithWebSockets.router.get('/wsRoute1', doSomething2); // processed as HTTP GET request

routerWithWebSockets.ws('/wsRoute1', (ws) => doSomethingWithWS1); // processed as Websocket upgrade request

routerWithWebSockets.ws('/wsRoute2', (ws) => doSomethingWithWS2); // processed as Websocket upgrade request

export default routerWithWebSockets.router;

【讨论】:

以上是关于为 Express 定义 Websocket 路由的主要内容,如果未能解决你的问题,请参考以下文章

NextJS,Express,WebSocket 握手期间出错:意外响应代码:200

路由 + Express 路由

路由 + Express 路由

浅析Express中的路由与应用模式

三Express 路由

防止 Angular 6 路由器覆盖 Express Server 中定义的路由