如何在 express 中创建 websocket 接口以及一些相关问题的处理

如何在 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 哇~

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部