express中创建 websocket 接口及问题解答
作者:程序媛李李李李李蕾
大多数文章里的方法是直接安装 express-ws
然后进行使用:
const express = require(`express`) const server = express() const expressWs = require(`express-ws`) expressWs(server) server.ws(`/aaa`, (ws, req) => { console.log(`socket`, req.testing) ws.send(`aaa`) ws.on(`message`, function(msg) { console.log(msg) }) }) server.get(`/bbb`, (req, res) => { res.json(`bbb`) }) server.listen(3040)
可以看到在上面创建了一个 ws://127.0.0.1:3040/aaa
这样的 websoket 接口, 然而当我们尝试 new WebSocket("ws://127.0.0.1:3040/ccc")
时, 也能连接上, 这虽然不会影响我们的业务, 但没有创建的接口能连上, 显然是很不好的.
在 express-ws 的 github 仓库和问题列表中看了很久没有找到解决方法, 然后就去看它的代码. 发现代码不多, 却拆分了很多文件, 然而代码没多少我却很难看懂.
整理 express-ws 源码
然后先合并一些分散在各个文件里的代码和注释:
function expressWs(app, httpServer, options = {}) { addWsMethod(app) const server = http.createServer(app) app.listen = function serverListen(...args) { return server.listen(...args) } const wsServer = new ws.Server({server}) wsServer.on(`upgrade`, async (req, socket, upgradeHead) => { const res = new http.ServerResponse(req) return server(req, res) }) wsServer.on(`connection`, (socket, request) => { if (`upgradeReq` in socket) { request = socket.upgradeReq } request.ws = socket request.wsHandled = false request.url = websocketUrl(request.url) const dummyResponse = new http.ServerResponse(request) dummyResponse.writeHead = function writeHead(statusCode) { if (statusCode > 200) { dummyResponse._header = `` // eslint-disable-line no-underscore-dangle socket.close() } } app.handle(request, dummyResponse, () => { if (!request.wsHandled) { socket.close() } }) }) } function trailingSlash(string) { let suffixed = string if (suffixed.charAt(suffixed.length - 1) !== `/`) { suffixed = `${suffixed}/` } return suffixed } function websocketUrl(url) { if (url.indexOf(`?`) !== -1) { const [baseUrl, query] = url.split(`?`) return `${trailingSlash(baseUrl)}.websocket?${query}` } return `${trailingSlash(url)}.websocket` } function addWsMethod(target) { if (!target.ws) { target.ws = function (route, ...middlewares) { const wrappedMiddlewares = middlewares.map((function (middleware) { return (req, res, next) => { if (req.ws) { req.wsHandled = true try { middleware(req.ws, req, next) } catch (err) { next(err) } } else { next() } } })) const wsRoute = websocketUrl(route) this.get(...[wsRoute].concat(wrappedMiddlewares)) return this } } }
把源码整理出来, 看起来确实没多少. 然而还是有些难看懂, 并且没有解决上面所说的没有创建的接口也会连接的问题, 那么我整理源代码有何用?
拿着源码改了半天, 还是没有解决. 所以, 再去一下层看看能不能找到方法.
于是就来到了 express-ws
使用的 ws
, 看 ws
的使用量和活跃度都十分可观, 应该能解决我的问题, 如果不能解决, 就是我的问题~~~
摒弃 express-ws
先脱离 express-ws
自身的那个思想, 仔细看了半天文档, 看到 readme 里有有个示例: 演示了从 ws 连接时如何根据路由分发到不同的 ws 服务.
import { createServer } from 'http'; import { parse } from 'url'; import { WebSocketServer } from 'ws'; const server = createServer(); const wss1 = new WebSocketServer({ noServer: true }); const wss2 = new WebSocketServer({ noServer: true }); wss1.on('connection', function connection(ws) { // ... }); wss2.on('connection', function connection(ws) { // ... }); server.on('upgrade', function upgrade(request, socket, head) { const { pathname } = parse(request.url); if (pathname === '/foo') { wss1.handleUpgrade(request, socket, head, function done(ws) { wss1.emit('connection', ws, request); }); } else if (pathname === '/bar') { wss2.handleUpgrade(request, socket, head, function done(ws) { wss2.emit('connection', ws, request); }); } else { socket.destroy(); } }); server.listen(8080);
分发服务例子里有了演示, 但是能不能做到 express 那样通过 app.ws('/a')
app.ws('/b')
的形式进行分发呢? 能不能实现像 express 一样支持路径参数呢?
当前 express-ws
是没有支持路径参数的, 并且已经有一年没有活跃了.
自己思考实现 express-ws 并解决它存在的问题
首先 app.ws 可以直接在 express 实例是通过 app.ws = fn
的实现. 然后 ws 对应的不同路由对应到不同的处理方法如何实现呢?
那就先以路由为 key, 然后处理方法为 value 保存起来, 到时候根据访问的 url 查找对应的 key/value 执行就行.
如何根据 url 查找 key? 我们在 ws 提供的示例中发现在 server.on('upgrade')
这一层可以进行查找和分发, 然后好像思路上没什么大问题, 测试了一下确实可以.
以下是代码:
function expressWs(app) { const server = http.createServer(app) server.on(`upgrade`, (req, socket, head) => { const obj = app.wsRoute[req.url] obj ? obj.wss.handleUpgrade(req, socket, head, ws => obj.mid(ws, req)) : socket.destroy() }) app.listen = (...arg) => server.listen(...arg) app.ws = (route, mid) => { app.wsRoute = app.wsRoute || {} app.wsRoute[route] = { wss: new Server({ noServer: true }), mid, } } }
使用方式依然是:
const express = require(`express`) const server = express() expressWs(server) server.ws(`/aaa`, (ws, req) => { console.log(`socket`, req.testing) ws.send(`aaa`) ws.on(`message`, function(msg) { console.log(msg) }) }) server.get(`/bbb`, (req, res) => { res.json(`bbb`) }) server.listen(3040)
测试了一下连接不存在的 ws 接口, 能按预期工作, 不存在就连接不上, 存在的能连接上, http 接口也能正常工作.
但是, 我的代码好像更少呢~~~
接下来要实现其他的功能就很简单了: 例如
- 支持 express 的路由模式, 例如
/ws/:id
- 支持读取 params, query 参数
为什么要折腾这个?
因为在重构 https://github.com/wll8/mockm
到此这篇关于express中创建 websocket 接口及问题解答的文章就介绍到这了,更多相关express创建 websocket 接口内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!