Next.JS 自定义服务器在尝试使用 Socket.io 时重新启动,地址已被使用 :::3000

Next.JS custom server restarting when trying to use Socket.io, address already in use :::3000

每当我尝试 运行 API 端点之一的端点中的函数 refreshStock() /api/seller/deactivate 时,它会给我这个错误:

Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2] (net.js:1318:16)
    at listenInCluster (net.js:1366:12)
    at Server.listen (net.js:1452:7)
    at C:\Users\***\Documents\GitHub\***\***\.next\server\pages\api\seller\deactivate.js:191:10
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command

看起来它正在尝试重新启动服务器,但它是在编译后发生的,是不是我做错了什么,我在 medium 上学习了几个教程,他们给出了相同类型的代码,只是不是 ES 模块。我想使用 ES 模块,因为它是我编写的数据库函数。

Server.js:

import express from 'express';
import { createServer } from 'http';
import next from 'next';
import models from './server/models';
import { genStock } from './server/lib/functions';
import { Server } from 'socket.io';

const port = parseInt(process.env.PORT || '3000', 10);
const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const nextHandler = nextApp.getRequestHandler();

const app = express();
const server = createServer(app);
const io = new Server(server);

const Users = models.users;

io.use(async (socket, next) => {
  const err = new Error('Unauthorized');
  err.data = { message: 'Unauthorized, please try again later.' };
  try {
    if (!socket.handshake.auth.token) return next(err);
    let user = await Users.findOne({
      where: {
        socket_token: socket.handshake.auth.token,
      },
    });
    if (!user) {
      console.log('unauthenticated socket');
      socket.disconnect();
      next(err);
    }
    await Users.update(
      { socket_id: socket.id },
      {
        where: {
          socket_token: socket.handshake.auth.token,
        },
      },
    );
    next();
  } catch (e) {
    console.log(e);
    next(e);
  }
});

io.on('connection', async (socket) => {
  // Works fine
  const stock = await genStock();
  socket.emit('updateStock', stock);
});

// Fails with address already in use :::3000
export async function refreshStock() {
  const stock = await genStock();
  io.emit('updateStock', stock);
}

nextApp.prepare().then(async () => {
  app.all('*', (req, res) => nextHandler(req, res));

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

这是为了在卖家停用帐户并向所有用户发送新库存后刷新库存。

/api/seller/deactivate

....
await refreshStock();
....

我明白了,我只是将 WebSocket 服务器和 next.js 分开了。我已将本地 IP 列入白名单,这些 IP 似乎只允许服务器到服务器的通信。虽然我认为这不是完全可靠的,因为很可能有更好的方式来进行这种类型的通信,但目前它是有效的。

/**
 * This server cannot be imported in /api folders, it won't work.
 * Although it can import other functions
 * */

import express from 'express';
import { createServer } from 'http';
import session from 'express-session';
import { Server } from 'socket.io';
import { genStock } from './server/lib/stockFunctions';
import { sessionStore } from './server/lib/session';
import passport from './server/lib/passport';
import models from './server/models';

const authorizedIPs = ['::1', '127.0.0.1', '::ffff:127.0.0.1'];

const Users = models.users;
const app = express();
const httpServer = createServer(app);

const io = new Server(httpServer, {
  cors: {
    origin: `http://localhost:3000`,
    methods: ['GET', 'POST'],
    credentials: true,
  },
});

const wrap = (middleware) => (socket, next) => middleware(socket.request, {}, next);
io.use(
  wrap(
    session({
      secret: "---",
      resave: false,
      saveUninitialized: true,
      cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        path: '/',
        sameSite: 'lax',
      },
      store: sessionStore,
    }),
  ),
);
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));

io.use(async (socket, next) => {
  const err = new Error('Unauthorized');
  err.data = { message: 'Unauthorized, please try again later.' };
  try {
    const user = socket.request.user;
    if (!user) return next(err);
    await Users.update(
      { socket_id: socket.id },
      {
        where: {
          id: user.id,
        },
      },
    );
    next();
  } catch (e) {
    console.log(e);
    next(e);
  }
});

io.on('connection', async (socket) => {
  const stock = await genStock();
  socket.emit('updateStock', stock);
});

app.post('/refresh-stock', async function (req, res) {
  const ip = req.ip;

  if (!authorizedIPs.includes(ip)) {
    console.log(ip);
    return res.status(401).json({ success: false });
  }

  const newStock = await genStock();
  io.emit('updateStock', newStock);
  return res.status(200).json({ success: true });
});

httpServer.listen(3001);

console.log(`> Websockets ready on http://localhost:3001`);