Как настроить работу Websockets, с использованием Socket.IO, на облачной платформе DigitalOcean App Platform?

Надеюсь, у вас отличный день.

Вступление

Вопрос касается deploy на DigitalOcean с использованием их App Platfrom, а также общего понимания devops.

Использую Node v18.12.1, Socket.IO v4.1.3 на сервере, Socket.IO v4.6.1 на клиенте.

Я уже некоторое время пытаюсь установить соединение через Socket.IO с серверным компонентом моей платформы приложений (App Platform), но пока безуспешно. К моему удивлению, на моей локальной машине все работает нормально. Помимо всего прочего обычные HTTP запросы к серверу на DigitalOcean также отрабатывают совершенно нормально на порт 3060. Т.е. проблема исключительно в Websockets на DigitalOcean (и, вероятно, моей криворукости).

Описание неисправности

После отправки события Socket.IO от клиента я получаю только следующее в Devtools -> Network -> Preview:

devtools,network preview

You need to enable JavaScript to run this app.

Читал также, что проблема может скрываться в HTTPS протоколе, который на DigitalOcean включен по-умолчанию. Однако в таком случае не совсем понятно почему отрабатывают остальные HTTP запросы к серверу.

Подробности

Я использую node:cluster для своего сервера. Этот код был взят из официальной документации Socket.IO для node:cluster.

...
// До этого момента идут разные require, подключения middleware...

if (cluster.isPrimary) {
  console.log("Primary cluster is running...");

  const httpServer = http.createServer(app);

  // setup sticky sessions
  setupMaster(httpServer, {
    loadBalancingMethod: "least-connection",
  });

  // setup connections between the workers
  setupPrimary();

  const primaryClusterPort = 3265;
  httpServer.listen(primaryClusterPort, () => {
    // Listen on the HTTP server in the worker process
    console.log(`Primary cluster running on port ${primaryClusterPort}...`);
  });

  const workerCount = 2;
  for (let i = 0; i < workerCount; i++) {
    cluster.fork();
  }

  cluster.on("online", function (worker) {
    console.log("Worker " + worker.process.pid + " is online");
  });

  cluster.on("exit", function (worker, code, signal) {
    console.log(
      "Worker " +
        worker.process.pid +
        " died with code: " +
        code +
        ", and signal: " +
        signal
    );
    console.log("Starting a new worker");
    cluster.fork();
  });
} else {
  const httpServer = http.createServer(app); // Create the HTTP server in the worker process

  const io = new Server(httpServer, {
    cors: {
      origin: "*", // Allow all origins
      methods: ["GET", "POST"], // Allow GET and POST methods
      credentials: true, // Allow credentials
    },
  });

  io.adapter(createAdapter());

  // setup connection with the primary process
  setupWorker(io);

  io.on("connection", (socket) => {
    socket.on("exportExcel", (data) => {
      console.log("Socket on `exportExcel`: ", data);

      // for test
      socket.emit("exportExcel", { path: "export905.xlsx" });
    });
  });

  httpServer.listen(PORT, () => {
    // Listen on the HTTP server in the worker process
    console.log(`App listening on port ${PORT}...`);
  });
}

Я надеюсь, что кто-нибудь сможет мне помочь, и я готов предоставить любую дополнительную информацию, если это необходимо. Заранее спасибо.


Ответы (1 шт):

Автор решения: Shadowalker

Ради проверки переписал свой код (с использованием API от Socket.IO) на API npm-пакета ws.


TL;DR

Проблемой для DigitalOcean App Platform является само наличие Socket.IO. Решение: Использовать другую библиотеку для сокетов (я выбрал ws).


Client (функция на button onClick):

  const protocol = window.location.protocol.includes(
    "https"
  )
    ? "wss"
    : "ws";
  const ws = new WebSocket(`${protocol}://${SOCKET_URL}`);

  ws.onopen = () => {
    let filtPost = parseFilterToQuery({
      filters,
      typeAnd,
    });

    const message = JSON.stringify({
      nameColl,
      filtrs: filtPost,
      cms,
    });

    ws.send(message);
  };

  ws.onmessage = function (data) {
    const aLink = document.createElement("a");
    aLink.href = SERVER_URL + data.data;
    aLink.target = "_blank";
    aLink.download = "export.xlsx";
    aLink.click();
  };

Обратите внимание на самую первую строку: const protocol... Это обязательное условие для того, чтобы на DigitalOcean мы подключались соответсвенно к wss (зашифрованному) сокет-соединению.

Server:

...
} else {
  const httpServer = http.createServer(app); // Create the HTTP server in the worker process

  const wss = new WebSocket.Server({ server: httpServer });

  wss.on("connection", function (ws) {
    ws.on("message", async function (message) {
      const parsedMessage = JSON.parse(message);
      exportExcel({
        ...parsedMessage,
        ws,
        userID: "test",
        cmsSlug: parsedMessage.cms,
        callback: (data) => {
          ws.send(data);
        },
      });

      console.log("received: %s", message);
    });
  });

  httpServer.listen(PORT, () => {
    // Listen on the HTTP server in the worker process
    console.log(`App listening on port ${PORT}...`);
  });
}
→ Ссылка