Node.js http-proxy 丢弃 websocket 请求

Posted

技术标签:

【中文标题】Node.js http-proxy 丢弃 websocket 请求【英文标题】:Node.js http-proxy drops websocket requests 【发布时间】:2013-12-20 12:44:17 【问题描述】:

好的,我已经花了一个多星期的时间试图弄清楚这一点,但无济于事,所以如果有人有线索,你就是英雄。这不是一个容易回答的问题,除非我是个笨蛋。

我正在使用 node-http-proxy 将粘性会话代理到在不同端口上运行的 16 个 node.js 工作人员。

我使用 Socket.IO 的 Web Sockets 来处理一堆不同类型的请求,也使用传统的请求。

当我通过 node-http-proxy 将服务器切换到代理时,有时会出现一个新问题,我的 Socket.IO 会话无法建立连接。

我真的无法在我的一生中稳定地复制它,打开它的唯一方法就是从多个客户端向服务器抛出大量流量。

如果我重新加载用户的浏览器,它有时可以重新连接,有时不能。

粘性会话

当我的应用基于每个工作人员进行身份验证时,我必须代理粘性会话,因此它根据其 Connect.SID cookie 路由请求(我正在使用 connect/express)。

好的,一些代码

这是我的 proxy.js 文件,它在节点中运行并路由到每个工作人员:

var http = require('http');
var httpProxy = require('http-proxy');

// What ports the proxy is routing to.
var data = 
  proxyPort: 8888,
  currentPort: 8850,
  portStart: 8850,
  portEnd: 8865,
;

// Just gives the next port number.
nextPort = function() 
  var next = data.currentPort++;
  next = (next > data.portEnd) ? data.portStart : next;
  data.currentPort = next;
  return data.currentPort;
;

// A hash of Connect.SIDs for sticky sessions.
data.routes = 

var svr = httpProxy.createServer(function (req, res, proxy) 

  var port = false;

  // parseCookies is just a little function
  // that... parses cookies.
  var cookies = parseCookies(req);  

  // If there is an SID passed from the browser.
  if (cookies['connect.sid'] !== undefined) 

    var ip = req.connection.remoteAddress;

    if (data.routes[cookies['connect.sid']] !== undefined) 

      // If there is already a route assigned to this SID,
      // make that route's port the assigned port.
      port = data.routes[cookies['connect.sid']].port;
     else 

      // If there isn't a route for this SID,
      // create the route object and log its
      // assigned port.
      port = data.currentPort;
      data.routes[cookies['connect.sid']] = 
        port: port,
      

      nextPort();
    

   else 

    // Otherwise assign a random port, it will/
    // pick up a connect SID on the next go.
    // This doesn't really happen.
    port = nextPort();
  

  // Now that we have the chosen port, 
  // proxy the request.
  proxy.proxyRequest(req, res, 
    host: '127.0.0.1',
    port: port
  );
).listen(data.proxyPort);

// Now we handle WebSocket requests.
// Basically, I feed off of the above route
// logic and try to route my WebSocket to the
// same server regular requests are going to.
svr.on('upgrade', function (req, socket, head) 

  var cookies = parseCookies(req);  
  var port = false;

  // Make sure there is a Connect.SID,
  if (cookies['connect.sid'] != undefined) 

    // Make sure there is a route...
    if (data.routes[cookies['connect.sid']] !== undefined) 

      // Assign the appropriate port.
      port = data.routes[cookies['connect.sid']].port;
     else 

      // this has never, ever happened, i've been logging it.
    
   else 

    // this has never, ever happened, i've been logging it.
  ;

  if (port === false) 

    // this has never happened...
  ;

  // So now route the WebSocket to the same port
  // as the regular requests are getting.
  svr.proxy.proxyWebSocketRequest(req, socket, head, 
    host: 'localhost',
    port: port
  );

);

客户端/现象

Socket 连接如下:

var socket = io.connect('http://whatever:8888');

登录大约 10 秒后,我在此侦听器上收到此错误,这并没有太大帮助。

socket.on('error', function (data) 
  // this is what gets triggered. ->
  // Firefox can't establish a connection to the server at ws://whatever:8888/socket.io/1/websocket/Nnx08nYaZkLY2N479KX0.
);

浏览器发送的 Socket.IO GET 请求永远不会回来 - 它只是挂起等待,即使在错误回来之后,所以它看起来像一个超时错误。服务器从不响应。

服务器端 - 工作人员

这是工作人员接收套接字请求的方式。很简单。所有工人都有相同的代码,所以你认为他们中的一个会收到请求并确认它......

app.sio.socketio.sockets.on('connection', function (socket) 
  // works... some of the time! all of my workers run this
  // exact same process.
);

总结

这是很多数据,我怀疑是否有人愿意面对它,但我完全被难住了,不知道接下来在哪里检查,接下来记录什么,以解决它。我已经尝试了我所知道的一切以查看问题所在,但无济于事。

更新

好的,我很确定问题出在node-http-proxy github homepage 上的这个声明中:

node-http-proxy 与 = 0.10兼容版本请查看caronte

我正在运行 Node.js v0.10.13,这种现象与 github 问题上的一些评论完全一样:它只是随机丢弃 websocket 连接。

我尝试实现 caronte,即“较新”的分支,但它根本没有记录在案,我已尽最大努力将他们的文档拼凑成一个可行的解决方案,但我无法让它转发 websockets,我的 Socket.IO 降级为轮询。

对于如何实现和工作还有其他想法吗? node-http-proxy 昨天有 8200 次下载!肯定有人在使用今年的 Node 版本并代理 websockets....

我正在寻找什么

我想完成一个代理服务器(最好是 Node),它代理多个 node.js 工作人员,并通过基于浏览器 cookie 的粘性会话路由请求。该代理需要稳定地支持传统请求以及 Web 套接字。

或者...

如果可行的话,我不介意通过集群节点工作人员完成上述任务。我唯一真正的要求是根据请求标头中的 cookie 维护粘性会话。

如果有比我尝试的更好的方法来完成上述工作,我完全赞成。

【问题讨论】:

您使用的是什么版本的 Node?看起来它可能与此有关:github.com/nodejitsu/node-http-proxy/pull/402 - 有一个带有修复的拉取请求,但显然这并不能解决所有问题。 v0.10.13 - 是不是太旧了? 老实说,我不太清楚它在哪里。它在 cmets 中提到他们仍然遇到 0.10.13 的问题。 你能解释一下你想用这个来完成什么吗?也许有更好的方法来做事。 很抱歉问你为什么首先需要粘性会话?如果是为了认证,通常可以通过使用redis store来解决。 【参考方案1】:

总的来说,我不认为 node 不是最常用的代理服务器选项,我使用nginx 作为 node 的前端服务器,这是一个非常好的组合。这里有一些instructions 来安装和使用 nginx 粘性会话模块。

它是一个轻量级的前端服务器,具有类似 json 的配置,可靠且经过很好的测试。

如果您想提供静态页面 css,nginx 也快得多。理想的配置缓存标头、根据域、粘性会话、压缩 css 和 javascript 等将流量重定向到多个服务器。

您还可以考虑像HAProxy 这样的纯负载平衡开源解决方案。无论如何,我不相信 node 是最好的工具,最好只用它来实现你的后端,并在它前面放置像 nginx 这样的东西来处理通常的前端服务器任务。

【讨论】:

感谢您的回复。有趣的是,现在 nginx 正是我所关注的。我唯一的问题是 ngingx 100% 为 linux 优化,不幸的是我的 Node.js 服务器运行在 Windows 环境中。所以为了让它工作,我必须在 Hyper-V 虚拟机上运行 nginx,这给原本简单的设置增加了复杂性。另外,我希望 nginx 成为一个静态文件服务器,但这意味着我必须将我的 web 目录放在 nginx 盒子上...... 听起来不错,是否适用于大容量应用程序? nginx 的 windows 版本可能已经提供了比所需更高的吞吐量,或者可以与在 windows 之上的 VM 上运行的 unix 版本相媲美,唯一的方法就是尝试一下。如果 VM 直接位于金属之上,那么它会更快,否则可能不值得。 Hyper-V 可能可以配置为访问主机操作系统上的文件夹,至少其他 VM 可以,以便更方便地更新站点而无需在 VM 内复制文件,我不确定这是否可以它更慢。 是的,如果考虑到可用硬件的数量,我相信它是高容量的。它是一个正在运行的内部单页应用程序,一旦优化,它可能会在页面加载的文件中下载大约 500kb 的有效负载,供 100 - 200 个用户使用。为了托管这个,我们每个组织都有一台服务器,具有 8 - 16 个 AMD 内核。此服务器还充当数据库服务器,并且此 SPA 是数据密集型的。这似乎是一个相对沉重的负担?我没有什么比较,所以要么是,要么是其他东西非常错误。 也许我也只是在 unix VM 上托管 Node.js ......似乎从 VM 访问原始服务器上的文件夹作为通过和服务器的想法会减慢它的速度。 ... @jhadesdev 这对我有用。最终在 linux VM 上使用 nginx。 +250【参考方案2】:

我同意六氰化物。对我来说,通过 redis 之类的服务或某种消息查询系统对工作人员进行排队是最有意义的。工作人员将通过 Web 节点(被代理)通过 Redis Pub/Sub 功能排队。工作人员会在错误、完成或通过“数据”事件实时流式传输数据时进行回调。也许看看图书馆kue。您也可以推出自己的类似库。 RabbitMQ 是另一个用于类似目的的系统。

如果您已经在使用该技术,我会使用 socket.io,但您需要使用工具来实现其预期目的。 Redis 或 MQ 系统将是最有意义的,并与 websockets(socket.io) 完美搭配以创建实时、有洞察力的应用程序。

通过 Elastic LoadBalancer for aws 支持会话关联(粘性会话),这支持 webSockets。 PaaS 提供商 (Modulus) 正是这样做的。还有 satalite 为 node-http-proxy 提供粘性会话,但我不知道它是否支持 webSockets。

【讨论】:

谢谢,这些都是很多好主意,我正在查看它们中的每一个,看看它们是否能够发挥作用。一个问题——你说我需要使用为其目的而设计的工具——在我的上述论述中,什么是不当使用?我好奇。服务端优化不是我的专长,所以我想知道我正在做的事情是否完全不合时宜。 也许我的思想有点封闭,但我假设您通过代理服务器通过 websockets 分配工作节点作业。首先,我不认为代理最适合用作 IPC 媒介。我也不觉得 websockets 不适合这个目的。我觉得,在他们目前的发展状态下,都是为此而生的。我并不认为您的想法很有趣,而且我完全支持推动技术创新,但我不认为您将所述技术具体用于您的预期用途的优点。 总而言之,我也同意 jhadesdev 的回答,尽管只是部分同意。具有 node-http-proxy 支持的 PaaS 非常适合灵活性,但是您将从专门针对从上到下进行负载平衡的技术中获得最大收益。 Node 不是一个好的负载均衡器,您可以对其特定操作进行编程非常好,但是有更好的解决方案。 Nginx 是一个很好的,但现在大多数人都部署在云上。对 AWS 负载平衡服务进行润色不会有什么坏处。你也可以部署在 Modulus 上,它们提供会话亲和性和 websockets。【参考方案3】:

我自己一直在研究与此非常相似的东西,目的是动态生成(和销毁)Node.js 集群节点。

免责声明:我仍然不建议使用 Node 进行此操作;对于您正在寻找的那种设计架构,nginx 更稳定,甚至更稳定的是 HAProxy(非常成熟,并且很容易支持粘性会话代理)。正如@tsturzl 指出的那样,有satellite,但鉴于下载量较低,我会谨慎行事(至少在生产环境中)。

也就是说,由于您似乎已经使用 Node 设置了所有内容,因此重建和重新架构可能比它的价值更多。因此,使用 NPM 安装 caronte 分支:

    使用npm uninstall node-proxy 和/或sudo npm -d uninstall node-proxy 删除您以前的http-node-proxy 主安装

    下载caronte分支.zip并解压。

    运行npm -g install /path/to/node-http-proxy-caronte 就我而言,安装链接已损坏,所以我不得不运行sudo npm link http-proxy

我已经使用他们的基本代理示例启动并运行了它——这是否解决了您的会话丢失问题,只有您自己知道。

【讨论】:

感谢您的回答!我真的很高兴你回答这个问题,因为我正在寻找其他尝试过 caronte 的人。我已经安装了 caronte 分支(我在上面的回答中提到了这一点),但我无法让它转发 Web Sockets。我的请求不断降级为长轮询。文档非常简陋,所以也许我不理解它,尽管我确实查看了源代码并尝试过。有什么方法可以让我看到你的工作示例,以便我可以看到我做错了什么? 回答你关于 nginx 的另一点,我现在已经启动并运行了 ngingx,但它在我的服务器上的 unix hyper-v vm 上运行,我正在寻找更清洁的替代部署我将在其中安装它的网站,所以很想看到 caronte 的工作。 我承认我没有尝试使用网络套接字 - 我想我误读并认为您在 安装 它时遇到了问题。我会再玩一些,看看我是否可以获得网络套接字转发。我也同意你对 nginx 的看法——功能强大,但不如轻量级和便携。 谢谢 - 如果你能弄清楚它会非常有帮助。 你能充实你的客户端和服务器的实现吗?我正在尝试将您构建的内容拼凑起来,但在不知不觉中,我可能会做一些完全不同的事情。

以上是关于Node.js http-proxy 丢弃 websocket 请求的主要内容,如果未能解决你的问题,请参考以下文章

express反向代理

nodejs中http-proxy使用小结

http-proxy 有问题

扩展执行繁重计算的 node.js websocket 服务器

为啥我的节点静态文件服务器丢弃请求?

redis pub/sub 与 node.js 中的 socket.io