如何在ExpressJS应用程序中使用ws库定义Websockets路由?将这两层并行设置是非常容易的,但是Websocket层将无法从ExpressJS中间件(如身份验证)中受益。我能找到的唯一实现是express-ws,由于不是最新的,它有严重的bug,并且严重依赖monkeypatching才能工作。
部分修改此回答。修改条目文件以包含以下内容:
/* 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功能的快速路由器示例,但逻辑可以提取以用于一次性
/* 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;