Nginx 反向代理 WebSocket 超时

Nginx Reverse Proxy WebSocket Timeout

我在 wowza 应用程序中使用 java-websocket 来满足我的 websocket 需求,并使用 nginx 进行 ssl,将请求代理到 java。

问题是服务器端的连接似乎正好在 1 小时后断开。客户端甚至不知道它已断开连接很长一段时间。我不想只调整 nginx 的超时时间,我想了解为什么连接会被终止,因为套接字一直在正常运行,直到它停止运行为止。

编辑: 忘记post配置:

location /websocket/ {
    proxy_set_header        X-Real-IP       $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    include conf.d/proxy_websocket;
    proxy_connect_timeout 1d;
    proxy_send_timeout 1d;
    proxy_read_timeout 1d;
}

其中包括配置:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass                      http://127.0.0.1:1938/;

很可能是因为您的 websocket 代理配置需要稍微调整一下,但既然您问了:

There are some challenges that a reverse proxy server faces in supporting WebSocket. One is that WebSocket is a hop‑by‑hop protocol, so when a proxy server intercepts an Upgrade request from a client it needs to send its own Upgrade request to the backend server, including the appropriate headers. Also, since WebSocket connections are long lived, as opposed to the typical short‑lived connections used by HTTP, the reverse proxy needs to allow these connections to remain open, rather than closing them because they seem to be idle.

在处理 websocket 代理的位置指令中,您需要包含 headers,这是 Nginx 给出的示例:

location /wsapp/ {
    proxy_pass http://wsbackend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
}

这现在应该可以工作了,因为:

NGINX supports WebSocket by allowing a tunnel to be set up between a client and a backend server. For NGINX to send the Upgrade request from the client to the backend server, the Upgrade and Connection headers must be set explicitly, as in this example

我还建议您查看 Nginx Nchan module,它将 websocket 功能直接添加到 Nginx 中。效果很好。

超时可能来自客户端、nginx 或后端。当你说它正在被削减时 "server side" 我认为那意味着你已经证明它不是客户端。您的 nginx 配置看起来不应该超时 1 day,因此只留下后端。

直接测试后端

我的第一个建议是您尝试直接连接到后端并确认问题仍然存在(将 nginx 排除在外以进行故障排除)。请注意,如果使用浏览器不可行,您可以使用 curl 等命令行实用程序来执行此操作。这是一个示例测试命令:

time curl --trace-ascii curl-dump.txt -i -N \
  -H "Host: example.com" \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: BOGUS+KEY+HERE+IS+FINE==" \
  http://127.0.0.1:8080

在我的(工作)案例中,运行 上面的示例无限期地保持打开状态(我手动使用 Ctrl-C 停止),因为 curl 和我的服务器都没有实现超时。但是,当我将其更改为通过 nginx 作为代理(默认超时为 1 分钟)时,如下所示,我在将近 1 分钟后看到来自 nginx 的 504 响应。

time curl -i -N --insecure \
  -H "Host: example.com" \
  https://127.0.0.1:443/proxied-path
HTTP/1.1 504 Gateway Time-out
Server: nginx/1.14.2
Date: Thu, 19 Sep 2019 21:37:47 GMT
Content-Type: text/html
Content-Length: 183
Connection: keep-alive

<html>
<head><title>504 Gateway Time-out</title></head>
<body bgcolor="white">
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>

real    1m0.207s
user    0m0.048s
sys 0m0.042s

其他想法

有人提到尝试 proxy_ignore_client_abort 但这应该没有任何区别,除非客户端正在关闭连接。此外,尽管这可能会使内部连接保持打开状态,但我认为它无法保持端到端流的完整性。

您可能想尝试 proxy_socket_keepalive,尽管这需要 nginx >= 1.15.6。

最后,WebSocket proxying doc 中有一条注释暗示了一个好的解决方案:

Alternatively, the proxied server can be configured to periodically send WebSocket ping frames to reset the timeout and check if the connection is still alive.

如果您可以控制后端并希望连接无限期保持打开状态,请定期向客户端发送 "ping" frames(假设使用 Web 浏览器,则客户端无需更改,因为它是作为规范的一部分实现的)应该防止连接因不活动而关闭(使得 proxy_read_timeout 不必要)无论它打开多长时间或涉及多少中间盒。