简体   繁体   English

在 Express.js 路由中发送 websocket 消息

[英]send a websocket message in a route, Express.js

The Goal:目标:

Let's say we have a marketplace full of shops.假设我们有一个充满商店的市场。 I'm creating a specific page localhost:3000/livePurchases/:storeId for a shop owner to monitor live notifications of when they got a new purchase.我正在为店主创建一个特定页面localhost:3000/livePurchases/:storeId来监控他们何时获得新购买的实时通知。

在此处输入图像描述

在此处输入图像描述

alert('you received a new purchase') should be triggered by a WebSocket when an item is purchased. alert('you received a new purchase')应在购买商品时由 WebSocket 触发。

The Problem:问题:

I need to set up WebSockets on my express server so that the websocket can be triggered somewhere in my code different from where the websocket server was set up.我需要在我的 express 服务器上设置 WebSocket,以便可以在我的代码中与设置 websocket 服务器的位置不同的地方触发 websocket。 But I don't understand how to do this.但我不明白如何做到这一点。

The route /newPurchase/:storeId would be requested by the browser of a customer after they successfully purchase an item.在成功购买商品后,客户的浏览器将请求路由/newPurchase/:storeId The websocket should send a message within the code of the route "/newPurchase/:storeId" (backend) to the websocket on "/livePurchases/:storeId" (frontend) where the shop owner can monitor live purchases. websocket 应在"/newPurchase/:storeId" (后端)的路由代码内向"/livePurchases/:storeId" (前端)上的 websocket 发送一条消息,店主可以在其中监控实时购买。

app.js

const express = require("express");

module.exports = (config) => {
  const app = express();

  app.post("/newPurchase/:storeId", (req, res, next) => {
    const { storeId } = req.params;
    // trigger websocket message to `localhost:3000/livePurchases/:storeId`
    // when client requests this route

  });

  return app;
};

But app.js is exported and run from another script, www.js .但是 app.js 是从另一个脚本www.js导出和运行的。 In real scenarios this is to connect a database before running the app.:在实际场景中,这是在运行应用程序之前连接数据库:

www.js

const app = require("../server/app")();

const port = process.env.PORT || "4000";
app.set("port", port);

app
  .listen(port)
  .on("listening", () =>
    console.log("info", `HTTP server listening on port ${port}`)
  );

module.exports = app;

So that means that the web socket server needs to be set up in www.js .这意味着需要在www.js中设置 web 套接字服务器。

Below is a notifier service I got it from this tutorial , which seemed like it was trying to solve the problem I have, but it didn't explain how to implement it.下面是我从本教程中获得的通知服务,它似乎试图解决我遇到的问题,但没有解释如何实现它。 It is a class that handles the websocket.它是一个处理 websocket 的 class。

NotifierService.js

const url = require("url");
const { Server } = require("ws");

class NotifierService {
  constructor() {
    this.connections = new Map();
  }

  connect(server) {
    this.server = new Server({ noServer: true });
    this.interval = setInterval(this.checkAll.bind(this), 10000);
    this.server.on("close", this.close.bind(this));
    this.server.on("connection", this.add.bind(this));
    server.on("upgrade", (request, socket, head) => {
      console.log("ws upgrade");
      const id = url.parse(request.url, true).query.storeId;

      if (id) {
        this.server.handleUpgrade(request, socket, head, (ws) =>
          this.server.emit("connection", id, ws)
        );
      } else {
        socket.destroy();
      }
    });
  }

  add(id, socket) {
    console.log("ws add");
    socket.isAlive = true;
    socket.on("pong", () => (socket.isAlive = true));
    socket.on("close", this.remove.bind(this, id));
    this.connections.set(id, socket);
  }

  send(id, message) {
    console.log("ws sending message");

    const connection = this.connections.get(id);

    connection.send(JSON.stringify(message));
  }

  broadcast(message) {
    console.log("ws broadcast");
    this.connections.forEach((connection) =>
      connection.send(JSON.stringify(message))
    );
  }

  isAlive(id) {
    return !!this.connections.get(id);
  }

  checkAll() {
    this.connections.forEach((connection) => {
      if (!connection.isAlive) {
        return connection.terminate();
      }

      connection.isAlive = false;
      connection.ping("");
    });
  }

  remove(id) {
    this.connections.delete(id);
  }

  close() {
    clearInterval(this.interval);
  }
}

module.exports = NotifierService;

Where I left off implementing the `NotifierService`我停止实施“NotifierService”的地方

I added the websocket server with the NotifierService in www.js我在 www.js 中添加了带有NotifierServicewww.js服务器

www.js with websockets added

const app = require("../server/app")();
const NotifierService = require("../server/NotifierService.js");
const notifier = new NotifierService();
const http = require("http");
const server = http.createServer(app);
notifier.connect(server);
const port = process.env.PORT || "4000";
app.set("port", port);

server
  .listen(port)
  .on("listening", () =>
    console.log("info", `HTTP server listening on port ${port}`)
  );

module.exports = app;

But now how do I send the websocket message from the /newPurchase route in app.js on the backend?但是现在如何从后端app.js中的/newPurchase路由发送 websocket 消息? If I create a new instance of NotifierService in app.js in order to use the notifierService.send method in the /newPurchase route, then the new NotifierService instance won't have access to the websocket connections because it would be a different instance than the one initiated on www.js .如果我在app.js中创建一个新的NotifierService实例,以便在/newPurchase路由中使用notifierService.send方法,那么新的NotifierService实例将无法访问 websocket 连接,因为它与一项在www.js上发起。

Front End:前端:

App.js

import React from "react";

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import LiveStorePurchases from "./LiveStorePurchases";

function App(props) {
  return (
    <div className="App">
      <Router>
        <Switch>
          <Route exact path="/livePurchases/:storeId">
            <LiveStorePurchases />
          </Route>
        </Switch>
      </Router>
    </div>
  );
}

export default App;

LivePurchaseServer.js

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

export default function LiveStorePurchases() {
  let { storeId } = useParams();
  const URL = "ws://127.0.0.1:4000?storeId=" + storeId;

  const [ws, setWs] = useState(new WebSocket(URL));

  useEffect(() => {
    ws.onopen = (e) => {
      newFunction(e);

      function newFunction(e) {
        alert("WebSocket Connected");
      }
    };

    ws.onmessage = (e) => {
      const message = e.data;
      alert(message);
    };

    return () => {
      ws.onclose = () => {
        alert("WebSocket Disconnected");
        setWs(new WebSocket(URL));
      };
    };
  }, [ws.onmessage, ws.onopen, ws.onclose, ws, URL]);

  return (
    <div
      style={{
        color: "red",
        fontSize: "4rem",
      }}
    >
      store: {storeId}
    </div>
  );
}

app.js : app.js

I was able to move the websocket instance to app.js instead of www.js .我能够将 websocket 实例移动到app.js而不是www.js Then I simply passed that instance around to other routes.然后我只是将该实例传递给其他路线。

const express = require("express");
const NotifierService = require("../server/NotifierService.js");
const notifier = new NotifierService();
const http = require("http");
const routes = require("./routes");

module.exports = (config) => {
  const app = express();
  const server = http.createServer(app); // websocket created
  notifier.connect(server);              // and connected here in app.js

  //   I moved POST /newPurchase to routes.js in order
  //   to demonstrate how the notifier instance can be
  //   passed around to different routes
  app.use(routes(notifier));

  return server;
};

routes.js : routes.js

I created a routes file routes.js to show that you could move the notifier instance around and call it from any route.我创建了一个路由文件routes.js以表明您可以移动通知程序实例并从任何路由调用它。

const express = require("express");

const router = express.Router();

// I moved "/newPurchase/:id" to this routes.js file to show that
// I could move the notifier instance around.

module.exports = (webSocketNotifier) => {
  router.post("/newPurchase/:id", (req, res, next) => {
    webSocketNotifier.send(req.params.id, "purchase made");
    res.status(200).send();
  });
  return router;
};

www.js : www.js

const server = require("../server/app")();

const port = process.env.PORT || "4000";

server
  .listen(port)
  .on("listening", () =>
    console.log("info", `HTTP server listening on port ${port}`)
  );

module.exports = server;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM