【发布时间】:2021-10-29 18:23:58
【问题描述】:
如何在 ExpressJS 应用程序中使用 ws 库为 Websocket 定义路由?并行设置两层非常容易,但是 Websocket 层将无法从 ExpressJS 中间件(例如身份验证)中受益。我能找到的唯一实现是express-ws,由于不是最新的,它存在严重的错误,并且严重依赖猴子补丁才能工作。
【问题讨论】:
如何在 ExpressJS 应用程序中使用 ws 库为 Websocket 定义路由?并行设置两层非常容易,但是 Websocket 层将无法从 ExpressJS 中间件(例如身份验证)中受益。我能找到的唯一实现是express-ws,由于不是最新的,它存在严重的错误,并且严重依赖猴子补丁才能工作。
【问题讨论】:
部分修改自此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;
【讨论】: