Node.js + Socket.io + Nginx 反向代理不起作用

3
我将设置一个Nginx反向代理到一个包括Socket.IO的NodeJS应用程序,该服务器还托管其他NodeJs应用程序。
NodeJS通过PM2在3001端口上运行。以下是Nginx配置:
server {
        listen 80;
        server_name iptv-staging.northpoint.org;
        location / {
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;

                proxy_pass http://localhost:3001;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }
}

当直接通过服务器的IP地址运行应用程序http://xxx.xxx.xxx.xxx:3001/时,一切都能正常运行。Socket.IO的Ping/Pong请求大约为50毫秒(默认pingTimeout为5000毫秒)。但是,当通过其DNS名称http://iptv-staging.northpoint.org访问该应用程序时,客户端报告了一个ping超时并断开连接。第一次尝试重新连接后,它会再次在第一次ping/pong请求时断开连接。
据我所知,问题必须与Ngnix反向代理以及WebSockets如何被路由有关。似乎服务器对ping请求的回复没有传输到客户端。但我无法确定原因。非常感谢任何帮助。

你解决了这个问题吗?我在使用nginx反向代理时遇到了类似的问题。 - user938363
1个回答

0

我曾经遇到过完全相同的问题。一位同事提供了答案,但说实话我并没有完全理解,所以不能为此获得任何荣誉,但我希望我能从我的代码中找到答案。

首先,您需要告诉nginx将传入的URI作为客户端看到的方式发送,方法是添加X-Real-URI头:

proxy_set_header X-Real-URI $request_uri;

这个路径与你的服务器上最终看到的路径之间的区别告诉你URI的基本路径。
然后,我在服务器上创建了一个API端点来返回URI信息。 这是告诉客户端连接时使用的地址的关键。
apiRoutes.get('/options', (req, res) => {

  Log.info('Received request for app options.');

  // This isn't applicable to you, just showing where options declared.
  let options = JSON.parse(JSON.stringify(config.get('healthcheck.options')));

  // Decide what port to use.  It might be a port for a dev instance or from Env
  if ((process.env.PORT !== options.port) && (process.env.PORT > 0)) {
    options.port = process.env.PORT;
  }

  // This is the important bit...
  var internalUri = req.originalUrl;
  var externalUri = req.get('X-Real-URI') || internalUri;
  options.basePath = externalUri.substr(0, externalUri.length - internalUri.length + 1);

  res.status(200).send(JSON.stringify(options));
});

我的客户端是一个React应用程序,你需要考虑如何实现,但这是我实现的方式。

这是我调用Options服务的“helper”函数...

export async function getOptions() {

  const res = await axios.get('/api/options');

  return res.data;

}

在我的React页面中,当页面加载时,我会调用这个函数...
componentDidMount() {

    getOptions()
        .then((res) => {
            this.setState({ port: res.port });

        // Call a client-side function to build URL.  Incl later.
        const socketUrl = getSocketUrl(res.port);

        console.log(`Attempting to connect to sockets at ${socketUrl}.`);

        // This is where it all comes together...
        const socket = io(socketUrl, { path: `${res.basePath}socket.io` });
        socket.on('connect', function () { console.log(`Connected to ${socketUrl}`); });

        socket.on('message', (result) => {
          console.log(result);
        });

        socket.on('data', (result) => {
          console.log(`Receiving next batch of results.`);

          const filteredResults = JSON.parse(result));

          // Do something with the results here...

        });

    });

.....

最后,getSocketUrl函数...
function getSocketUrl(port) {

  console.log(`${window.location.hostname}`);

  if (window.location.hostname.toString().includes('local')) {
    return `localhost:${port}`;
  }

  return `${window.location.hostname}`;

}

这是一个有趣的方法。我尝试了一下(进行了一些修改),但是客户端basePath在我的笔记本电脑上(当socketio正常工作时)和通过nginx在我的暂存服务器上都返回“/”(在原始问题中它没有工作)。当您实施此方法时,基本路径是否在开发机器和暂存(服务器)环境之间更改? - jlommori
它确实会改变。开发服务器是一个无意义的服务器名称,但在生产环境中使用NGinx时,它具有有意义的域名。此外,开发服务器有一个端口号,而生产环境没有,或者说端口号被生产环境的域名掩盖了。就像你一样,我的原始套接字解决方案在开发环境中很好,但在生产环境中无法连接。这是高级开发人员使用的解决方案,以便套接字可以在任何服务器上工作。 - Chris Adams
值得注意的是,生产环境中只有一个应用程序实例,并且不使用端口范围进行负载平衡,因为这会增加进一步的复杂性。 - Chris Adams
我尝试了几种组合,但无法在我的环境中使其工作。现在打算使用Docker对项目进行容器化,以便获得唯一的IP地址而不使用反向代理,这应该可以解决问题。感谢您的建议! - jlommori
祝你好运,很抱歉它没有帮助到你。 - Chris Adams
1
这个答案本应该有效,但我们缺少一个关键部分:在你的nginx配置中,你需要告诉nginx发送X-Real-URI头部,因为它不是标准头部 - 我会编辑一下... - randomsock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接