WebSocket 测试
从浏览器开一条 WebSocket,送出帧并看服务器回什么。直接连到你的服务器,所以 localhost 也连得上;1006 会解释而不是只印出来。
在你的浏览器中运行
收发记录
- 目前没有记录。
你的浏览器直接连到你输入的地址,不会经过本站——这也是为什么本机地址也连得上。
关闭代码
| 代码 | 意义 |
|---|---|
| 1000 | Normal closure |
| 1001 | Going away |
| 1002 | Protocol error |
| 1003 | Unsupported data |
| 1005 | No status received服务器不会发送 |
| 1006 | Abnormal closure服务器不会发送 |
| 1007 | Invalid frame payload data |
| 1008 | Policy violation |
| 1009 | Message too big |
| 1010 | Mandatory extension missing |
| 1011 | Internal server error |
| 1012 | Service restart |
| 1013 | Try again later |
| 1014 | Bad gateway |
| 1015 | TLS handshake failed服务器不会发送 |
连线是你的浏览器发起的,不是本站
这条连线由你自己的浏览器直接连到你输入的地址,完全不经过本站。这件事有两个值得知道的后果:
ws://localhost:8080连得上。 跑在你这台机器、或你公司内网上的服务器,在这里连得到,即使 网际网络上没有任何服务器连得到它。那些把连线绕过自家后端的测试站做不到这件事。- 你送出去的东西,只有你的服务器看得到。 路径上没有第三方,因为设计上就没有第三方。
关闭代码 1006——大多数人是为了它来的
1006 不是你的服务器说的话。你的服务器送不出这个码,规格明文禁止,1005 和 1015 也一样。
它是浏览器在「连线结束了,但没有收到关闭帧」时所报的,意思仅止于此:连线死了,而没有人说原因。
下面这串里的某一个,而 WebSocket API 不会告诉你是哪一个:
- 那个主机和端口上根本没有东西在听。
- TLS 失败——
wss://那端的证书过期、自签,或域名对不上。 - 你和服务器之间的代理服务器或负载均衡器没有把 upgrade 转过去。
- 服务器行程挂了,或连到一半网络断了。
- 服务器用一个 HTTP 状态码、而不是
101,回绝了这次握手。 - 你提出了子协定,而服务器没有回一个给你——见下方。
API 是刻意不给原因的。 一个网页不可以有能力分辨「连线被拒」和「路由不通」,因为这个差别足以 让网页扫描访客的内网。不是浏览器知道却不给你,是浏览器刻意不往上传。
真正的错误通常在浏览器自己的控制台里,而那个地方 JavaScript 碰不到。打开开发者工具,看 控制台和网络分页的 WS 标签,常常就能看到那次握手实际拿到的 HTTP 状态。
混合内容:在任何流量之前就失败
这个页面是以 HTTPS 提供的,而安全页面不允许开启纯 ws:// 连线。浏览器会在一个字节送出去之前
就挡下来——然后把它报成一般的连线失败,看起来跟服务器挂掉一模一样。
这个页面在开启连线之前就先检查,并直接把原因说出来。
localhost 是例外:浏览器把环回地址视为安全环境,所以从 HTTPS 页面用 ws:// 测本机开发服务器
是可以的。其他地址一律要 wss://。
握手,以及代理服务器为什么会弄坏它
WebSocket 一开始是一个普通的 HTTP GET,带着 Upgrade: websocket 和 Connection: Upgrade,只有
在服务器回 101 Switching Protocols 时才算成功。回任何其他状态码都到此为止。
几乎每一个「本机好好的、上线就烂掉」的 WebSocket 问题,都是反向代理把这两个头部吃掉了。nginx 需要明确把它们加回去:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
proxy_http_version 1.1 这行是关键——默认是 1.0,而 1.0 根本没有 upgrade 机制。超时那行也是:
nginx 默认 60 秒就会关掉闲置连线,症状是「连线一切正常,然后刚好一分钟后死掉」。
子协定
第二个选填栏位会送出 Sec-WebSocket-Protocol 头部,按偏好顺序提出一份清单。服务器挑一个回
给你,页面上会显示挑中的是哪一个。实务上会遇到的是 graphql-ws、mqtt、json。
提出一个服务器没有回应的子协定,连线会直接死掉,而这一点很常让人卡住。服务器一个都不认得
时,它会照样完成握手、但不回这个头部——而 RFC 6455 接着要求客户端必须让这条连线失败。浏览器
就是这么做的,于是你拿到 1006,跟「服务器根本没开」拿到的是同一个码。
所以填了子协定就秒断、清空就连得上,那不是服务器坏了,是那台服务器不讲你指定的那个子协定。
你不能设头部,而这件事决定了验证怎么做
浏览器的 WebSocket API 只收一个网址和一份子协定清单,就这样。没有办法送出 Authorization
头部——这里不行,任何浏览器里都不行,换哪个库都不行。
所以服务器端的验证只能走别的路:
- Cookie,会自动跟着该来源的连线送出。最简单,但要注意 WebSocket 握手不受 CORS 保护, CSRF 那一类的顾虑还在。
- 放在查询字符串里的 token,
wss://host/ws?token=…。用的人很多,代价是它会进到访问记录和 代理服务器的记录里。请用短效期的 token。 - 连线建立后的第一则消息,服务器在收到之前什么都不做。最麻烦,也最干净。
有些库看起来能从浏览器送 Authorization 头部。它们没有,它们是把它塞进第一个帧里。
关闭代码
上面的表格列出了全部。要记的是这个结构:
- 1000–1015 由协定定义。
1000是正常关闭、1001是页面或服务器要离开、1009是帧超过 大小上限、1011是服务器内部错误。 - 3000–3999 是库与框架向 IANA 注册的。
- 4000–4999 是你的。想让某个关闭代表你自己应用程序里的某件事,就用这一段——不会有别的东西 来撞。
浏览器只被允许送出 1000 或 3000–4999 之间的代码。从这个页面按中断,送出的是 1000。
相关
我的 IP 地址是你的服务器看到的东西,
CSR 产生器处理 wss:// 背后那张证书,
帧收回来之后可以用 JSON 格式化读。