Ratchet 没有从浏览器接收 WebSocket 数据包?
Ratchet is not receiving WebSocket packet from browser?
我正在尝试使用 Ratchet 通过 WebSockets 测试简单的 ping & pong 响应。我的 WebSocket 服务器 没有 看到来自我的 Web 浏览器的 WebSocket 客户端的任何响应 但是 WebSocket 服务器发送的数据包浏览器看得很好。我正在寻找原因。
我目前的猜测是:
- 我缺少一些 HTTP header(s)
- 我必须在浏览器上对数据包进行编码
wsclient.send(encodeFrame("{'action': 'pong'}"));
- CloudFlare 无法将 WS 流中的数据包识别为有效数据包并对其进行处理
- EC2 实例中的 CloudFlare 或 nginx 正在做一些奇怪的缓冲
- Ratchet 无法识别最低 IOServer 级别上的数据包并对其进行处理
- 但我从来没有从这个级别得到任何错误或异常
设置:
- Linux 服务器@亚马逊 EC2
- DNS @ CloudFlare 开启免费计划和加速 + 开启强制 HTTPS 重定向
- HTTP 服务器是 nginx
- nginx 上没有 HTTPS(Cloudflare 重定向 HTTPS -> EC2 HTTP)
- WebSocket 服务器(Ratchet)运行 位于 EC2
127.0.0.1:65000
- Nginx 重定向
/api
到 127.0.0.1:65000
(Ratchet)
在 EC2 实例上使用自己的 WebSocket 客户端进行测试:
127.0.0.1:65000
工作正常
<Amazon NAT IP of instance>:80
工作正常
<Amazon public IP of instance>:80
工作正常
<CloudFlare IP of Amazon public IP>:80
在应用程序实现级别连接到 WebSocket 服务器,但在任何级别(应用程序、WsServer、HTTPServer)onMessage
方法上都看不到数据包
<CloudFlare IP of Amazon public IP>:443
给出 400 Bad Request
因为测试客户端只是简单的 TCP 流
从本地机器测试:
直接连接到CloudFlare缓存IP提供的主机。 Dojox.Socket 连接到 wss://host/api
。在 Ratchet 的应用程序实现级别上再次看到连接(onOpen
已触发)。我的浏览器可以正常接收 ping
数据包,因此从 Ratchet 发送数据正常。
但随后我尝试从浏览器向 ping
发送 pong
回复,并且 onMessage
方法在 Ratched 的任何级别上都不会被触发。连接保持打开状态,如果我用 Fiddler 观看,ping 和 pong 都会不断发送,但 WebSocket 服务器永远不会收到这些 pong (onMessage
)。
跟随 Fiddler 的 WebSocket 流显示来自浏览器的 pongs 有 "Data masked by key: <some hex>
" 但来自 Ratched 的 ping 没有 被屏蔽。
连接摘要:
页面加载:
本地机器http://host/ → CloudFlare HTTPS redirect https://host/ → http://host-with-amazon-public-ip/ → http://host-with-amazon-NAT-ip/→HTML+加载wss WebSocket连接的JS页面/api
与 /api
的 WebSocket 连接:
CloudFlare HTTPS 重定向 wss://host/api → http://host-with-amazon-public-ip/api → http://host-with-amazon-NAT-ip/api → 本地服务器 nginx 重定向 /api → 127.0.0.1:65000
→ 连接升级 → WebSocket 流 → WebSocket 流 for web浏览器
nginx.conf
server {
listen 80;
server_name test.example.com;
root /home/raspi/test/public;
autoindex off;
index index.php;
access_log /home/raspi/test/http-access.log;
error_log /home/raspi/test/http-error.log notice;
location / {
index index.php;
try_files $uri $uri/ /index.php?$args;
}
location /api {
access_log /home/raspi/test/api-access.log;
error_log /home/raspi/test/api-error.log;
expires epoch;
proxy_ignore_client_abort on;
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_pass http://127.0.0.1:65000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "keep-alive, Upgrade";
proxy_set_header Upgrade "websocket";
proxy_set_header Accept-Encoding "gzip, deflate";
proxy_set_header Sec-WebSocket-Extensions "permessage-deflate";
proxy_set_header Sec-WebSocket-Protocol "game";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ^~ /js/app/ {
try_files $uri /;
expires epoch;
add_header Cache-Control "no-cache" always;
}
location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt)$ {
try_files $uri /;
access_log off;
expires max;
}
location = /robots.txt { access_log off; log_not_found off; }
location = /favicon.ico { access_log off; log_not_found off; }
location ~ /\. { access_log off; log_not_found off; deny all; }
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Web 套接字目前仅在商业和企业级别可用。你提到你在免费计划中。这就是问题所在。
问题是您的提供商拒绝连接和更新 headers。可以去Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php修改代码为:
...
public function verifyAll(RequestInterface $request) {
$passes = 0;
$passes += (int)$this->verifyMethod($request->getMethod());
$passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion());
$passes += (int)$this->verifyRequestURI($request->getPath());
$passes += (int)$this->verifyHost((string)$request->getHeader('Host'));
$passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade'));
$passes += (int)$this->verifyConnection((string)$request->getHeader('Connection'));
die((string)$request);
...
并且您会看到结果请求没有必填字段;
解决方法:重写 WsServer::onOpen 函数并将该字段添加到手动请求。但这是不安全的...
我正在尝试使用 Ratchet 通过 WebSockets 测试简单的 ping & pong 响应。我的 WebSocket 服务器 没有 看到来自我的 Web 浏览器的 WebSocket 客户端的任何响应 但是 WebSocket 服务器发送的数据包浏览器看得很好。我正在寻找原因。
我目前的猜测是:
- 我缺少一些 HTTP header(s)
- 我必须在浏览器上对数据包进行编码
wsclient.send(encodeFrame("{'action': 'pong'}"));
- CloudFlare 无法将 WS 流中的数据包识别为有效数据包并对其进行处理
- EC2 实例中的 CloudFlare 或 nginx 正在做一些奇怪的缓冲
- Ratchet 无法识别最低 IOServer 级别上的数据包并对其进行处理
- 但我从来没有从这个级别得到任何错误或异常
设置:
- Linux 服务器@亚马逊 EC2
- DNS @ CloudFlare 开启免费计划和加速 + 开启强制 HTTPS 重定向
- HTTP 服务器是 nginx
- nginx 上没有 HTTPS(Cloudflare 重定向 HTTPS -> EC2 HTTP)
- WebSocket 服务器(Ratchet)运行 位于 EC2
127.0.0.1:65000
- Nginx 重定向
/api
到127.0.0.1:65000
(Ratchet)
在 EC2 实例上使用自己的 WebSocket 客户端进行测试:
127.0.0.1:65000
工作正常<Amazon NAT IP of instance>:80
工作正常<Amazon public IP of instance>:80
工作正常<CloudFlare IP of Amazon public IP>:80
在应用程序实现级别连接到 WebSocket 服务器,但在任何级别(应用程序、WsServer、HTTPServer)onMessage
方法上都看不到数据包<CloudFlare IP of Amazon public IP>:443
给出400 Bad Request
因为测试客户端只是简单的 TCP 流
从本地机器测试:
直接连接到CloudFlare缓存IP提供的主机。 Dojox.Socket 连接到 wss://host/api
。在 Ratchet 的应用程序实现级别上再次看到连接(onOpen
已触发)。我的浏览器可以正常接收 ping
数据包,因此从 Ratchet 发送数据正常。
但随后我尝试从浏览器向 ping
发送 pong
回复,并且 onMessage
方法在 Ratched 的任何级别上都不会被触发。连接保持打开状态,如果我用 Fiddler 观看,ping 和 pong 都会不断发送,但 WebSocket 服务器永远不会收到这些 pong (onMessage
)。
跟随 Fiddler 的 WebSocket 流显示来自浏览器的 pongs 有 "Data masked by key: <some hex>
" 但来自 Ratched 的 ping 没有 被屏蔽。
连接摘要:
页面加载:
本地机器http://host/ → CloudFlare HTTPS redirect https://host/ → http://host-with-amazon-public-ip/ → http://host-with-amazon-NAT-ip/→HTML+加载wss WebSocket连接的JS页面/api
与 /api
的 WebSocket 连接:
CloudFlare HTTPS 重定向 wss://host/api → http://host-with-amazon-public-ip/api → http://host-with-amazon-NAT-ip/api → 本地服务器 nginx 重定向 /api → 127.0.0.1:65000
→ 连接升级 → WebSocket 流 → WebSocket 流 for web浏览器
nginx.conf
server {
listen 80;
server_name test.example.com;
root /home/raspi/test/public;
autoindex off;
index index.php;
access_log /home/raspi/test/http-access.log;
error_log /home/raspi/test/http-error.log notice;
location / {
index index.php;
try_files $uri $uri/ /index.php?$args;
}
location /api {
access_log /home/raspi/test/api-access.log;
error_log /home/raspi/test/api-error.log;
expires epoch;
proxy_ignore_client_abort on;
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_pass http://127.0.0.1:65000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "keep-alive, Upgrade";
proxy_set_header Upgrade "websocket";
proxy_set_header Accept-Encoding "gzip, deflate";
proxy_set_header Sec-WebSocket-Extensions "permessage-deflate";
proxy_set_header Sec-WebSocket-Protocol "game";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ^~ /js/app/ {
try_files $uri /;
expires epoch;
add_header Cache-Control "no-cache" always;
}
location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt)$ {
try_files $uri /;
access_log off;
expires max;
}
location = /robots.txt { access_log off; log_not_found off; }
location = /favicon.ico { access_log off; log_not_found off; }
location ~ /\. { access_log off; log_not_found off; deny all; }
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Web 套接字目前仅在商业和企业级别可用。你提到你在免费计划中。这就是问题所在。
问题是您的提供商拒绝连接和更新 headers。可以去Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php修改代码为:
...
public function verifyAll(RequestInterface $request) {
$passes = 0;
$passes += (int)$this->verifyMethod($request->getMethod());
$passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion());
$passes += (int)$this->verifyRequestURI($request->getPath());
$passes += (int)$this->verifyHost((string)$request->getHeader('Host'));
$passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade'));
$passes += (int)$this->verifyConnection((string)$request->getHeader('Connection'));
die((string)$request);
...
并且您会看到结果请求没有必填字段;
解决方法:重写 WsServer::onOpen 函数并将该字段添加到手动请求。但这是不安全的...