news 2026/6/15 19:49:16

从HTTP到WebSocket:如何正确实现协议升级(can ‘upgrade‘ only to ‘websocket‘)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从HTTP到WebSocket:如何正确实现协议升级(can ‘upgrade‘ only to ‘websocket‘)


背景痛点:为什么浏览器突然翻脸

初学 WebSocket 时,我刷新页面看到控制台飘红
Error: can upgrade only to websocket
第一反应是“我明明写了ws://呀?”
其实这句话不是浏览器傲娇,而是服务器在握手阶段拒绝升级。
触发场景通常只有三类:

  • 少了Connection: UpgradeUpgrade: websocket
  • 服务端返回的状态码不是 101 Switching Protocols
  • 反向代理(Nginx、Kong 等)把 Upgrade 头吃掉

只要任意一项不满足,浏览器就会抛出这条错误,后续帧直接罢工。
理解 HTTP 升级机制后,你会发现 WebSocket 并不是“另一端口”,而是“同端口、不同语法”——先假装自己是 HTTP,拿到 101 车票后,再“变脸”成二进制帧协议。

技术对比:长轮询、SSE 还是 WebSocket?

维度HTTP 长轮询Server-Sent EventsWebSocket
协议HTTP/1.1HTTP/1.1HTTP→Upgrade→WS
方向双向模拟仅服务端推送真正双向
延迟1~3 s(轮询间隔)<1 s<100 ms
开销每次带 HTTP 头带少量头帧头仅 2~14 B
适用场景低版本 IE 兼容新闻/股价推送实时游戏、通话、协同编辑

一句话总结:
“能 Upgrade 就别轮询,SSE 是单向备胎,WebSocket 才是双向正主。”

核心实现:让服务器说“101”

1. 标准握手流程(必须字段)

客户端请求头:

GET /chat HTTP/1.1 Host: example.com:8080 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13

服务端校验后返回:

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Key→Accept 的算法固定:
base64(sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
任何偏差都会触发can upgrade only to websocket

2. Node.js 示例(ws 库)

安装:

npm i ws

server.js:

const WebSocket = require('ws'); const http = require('http'); // 先跑一个普通 HTTP 服务器 const server = http.createServer((req, res) => { res.writeHead(200); res.end('WebSocket upgrade server'); }); const wss = new WebSocket.Server({ noServer: true }); // 监听协议升级事件 server.on('upgrade', (request, socket, head) => { // 手动校验必要头 if ( request.headers.upgrade !== 'websocket' || !request.headers.connection.includes('Upgrade') ) { socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); socket.destroy(); return; } wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request); }); }); wss.on('connection', (ws) => { console.log('[WS] 客户端已连接'); ws.on('message', (data) => { console.log('[WS] 收到:%s', data); ws.send(`服务端回声:${data}`); }); ws.on('error', (err) => console.error('[WS] 异常:', err.message)); }); server.listen(8080, () => console.log('HTTP+WS 服务已启动于 8080'));

关键注释已写中文,异常直接try/catch会漏掉帧级错误,因此统一在error事件里处理。

3. Python 示例(websockets 库)

安装:

pip install websockets

server.py:

import asyncio import websockets import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s") async def echo(websocket, path): remote = websocket.remote_address logging.info(f"[WS] 客户端 {remote} 连接成功") try: async for msg in websocket: logging.info(f"[WS] 收到:{msg}") await websocket.send(f"服务端回声:{msg}") except websockets.exceptions.ConnectionClosed: logging.info(f"[WS] 客户端 {remote} 已断开") except Exception as e: logging.error(f"[WS] 异常:{e}") # 官方库已封装好 101 校验,直接启动即可 start_server = websockets.serve(echo, "0.0.0.0", 8081) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()

如果想手动校验 Upgrade 头,可在process_request钩子内拦截,返回426 Upgrade Required

生产考量:别让握手成功倒在最后一公里

  1. 连接超时与重连

    • 建议客户端 55 秒无响应就主动重连,避开默认 60 秒 NAT 超时
    • 服务端启用ping/pong心跳,Node 版ws自动回pong,Python 需await websocket.pong()
  2. WSS 加密配置

    • 证书放在反向代理层最省事,Nginx 监听 443,后端 8080 走裸 WS 即可
    • 若直连,需要wss://+ssl.create_default_context(),别忘了把中间证书链补全
  3. 负载均衡器特殊配置
    Nginx 示例:

map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl; location /ws { proxy_pass http://upstream_ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 300s; # 给心跳留时间 } }

漏掉任何一行,都会把 Upgrade 头吞掉,浏览器继续报错。

避坑指南:426/400/Origin 一个都别放过

  • 426 Upgrade Required
    服务端显式告诉客户端“我拒绝非 WebSocket 流量”,检查是否误把/ws当普通 HTTP 路由
  • 400 Bad Request
    常见把Sec-WebSocket-Key大小写写错,或Accept计算错误
  • Origin 校验陷阱
    浏览器一定会带 Origin 头,但 Node 默认不检查。生产环境务必做白名单,防止被恶意网页跨域连进来
  • 心跳间隔
    内网 30 s,公网 45~60 s 足够,太频繁浪费流量,太长容易被防火墙 RST

延伸思考:QUIC 时代的 WebTransport

WebSocket 建立在 TCP 之上,队头阻塞依旧存在。
Chrome 最新版已开放 WebTransport API,走 HTTP/3 over QUIC,天然多路复用、0-RTT 握手。
思路相同:先fetch()协商,再createBidirectionalStream()拿到读写流。
如果你已经把 Upgrade 玩熟,不妨把实验目标换成 WebTransport,提前踩坑,等标准落地就能无缝迁移。

写在最后:把“升级”真正跑通

从 HTTP 到 WebSocket,其实就是一次“换票上车”的小动作,但票根(101 状态)、身份证(Upgrade 头)、座位号(Sec-WebSocket-Key)一个都不能少。
我在本地调试通过后,第一时间把代码丢到线上,结果还是被 Nginx 打脸——忘了加proxy_set_header,浏览器继续can upgrade only to websocket
踩完坑最大的感受是:协议文档写得很细,但魔鬼藏在“反向代理默认不转发”这种小字里。

如果你想亲手把“耳朵、大脑、嘴巴”串成一条完整的实时通话链路,又懒得自己搭 ASR、LLM、TTS,可以试试这个动手实验:
从0打造个人豆包实时通话AI
实验把火山引擎的豆包系列模型都封装好了,WebSocket 升级部分也给了现成模板,小白直接跑脚本就能在浏览器里跟 AI 语音聊天。
我跟着做完,把聊天角色改成“猫娘”音色,30 分钟搞定,比自己东拼西凑省了不少时间。
升级协议只是第一步,真正的乐趣是让 AI 开口说话那一刻——祝你也能一次 101,永不 426。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 13:38:38

RMBG-2.0 Java开发指南:SpringBoot集成教程

RMBG-2.0 Java开发指南&#xff1a;SpringBoot集成教程 1. 引言 在当今数字内容爆炸式增长的时代&#xff0c;图像处理已成为许多应用的核心需求。无论是电商平台的商品展示、社交媒体的内容创作&#xff0c;还是企业文档的视觉呈现&#xff0c;高质量的图像背景移除功能都能…

作者头像 李华
网站建设 2026/6/15 13:36:49

AI 辅助开发实战:高效完成数字图像处理毕业设计的工程化路径

1. 学生常见痛点&#xff1a;算法跑通≠项目能跑 做数字图像处理毕设&#xff0c;很多同学把 80% 时间花在“调通算法”上&#xff0c;结果最后一周打包部署时才发现&#xff1a; 脚本里全局变量乱飞&#xff0c;换台电脑路径全崩一张 4K 图直接把 8 GB 笔记本内存吃满&#…

作者头像 李华
网站建设 2026/6/15 13:38:44

面试评估工具:候选人紧张/自信情绪AI自动评分

面试评估工具&#xff1a;候选人紧张/自信情绪AI自动评分 在真实招聘场景中&#xff0c;面试官常面临一个隐性但关键的挑战&#xff1a;如何客观捕捉候选人言语背后的情绪状态&#xff1f;一位候选人说“我很有信心”&#xff0c;但语调发紧、语速过快、频繁停顿——这真的是自…

作者头像 李华
网站建设 2026/6/15 13:35:22

Hunyuan 1.8B模型适配移动端:Android集成部署案例

Hunyuan 1.8B模型适配移动端&#xff1a;Android集成部署案例 1. 为什么是HY-MT1.5-1.8B&#xff1f;轻量不等于将就 你有没有遇到过这样的场景&#xff1a;在出差路上想把一段藏语会议纪要快速翻成中文&#xff0c;手机没网、翻译App卡顿、专业术语翻得牛头不对马嘴&#xf…

作者头像 李华