news 2026/4/23 16:10:53

从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/4/23 13:30:08

ollama部署Phi-4-mini-reasoning性能评测:CPU/GPU推理速度与准确率实测

ollama部署Phi-4-mini-reasoning性能评测&#xff1a;CPU/GPU推理速度与准确率实测 1. 为什么关注Phi-4-mini-reasoning这个小模型 你可能已经用过不少大模型&#xff0c;动辄几十GB显存占用、需要高端GPU才能跑起来。但有没有想过&#xff1a;一个真正轻量、能在普通笔记本甚…

作者头像 李华
网站建设 2026/4/23 10:49:51

自动驾驶多传感器同步仿真:操作指南与误差分析

以下是对您提供的技术博文《自动驾驶多传感器同步仿真:操作指南与误差分析》的 深度润色与专业重构版本 。本次优化严格遵循您的全部要求: ✅ 彻底去除AI痕迹,语言自然、老练、有工程师现场感 ✅ 摒弃模板化结构(无“引言/概述/总结”等刻板标题),以逻辑流驱动全文 …

作者头像 李华
网站建设 2026/4/23 10:48:39

万物识别镜像conda环境激活失败?常见原因分析

万物识别镜像conda环境激活失败&#xff1f;常见原因分析 你是否在运行“万物识别-中文-通用领域”镜像时&#xff0c;刚打开终端就卡在了第一步——conda activate py311wwts 报错&#xff1f;输入命令后只看到 CommandNotFoundError: py311wwts is not a conda environment …

作者头像 李华
网站建设 2026/4/23 10:50:12

Qwen3:32B在Clawdbot中的企业级功能:审计日志、会话持久化与权限分级

Qwen3:32B在Clawdbot中的企业级功能&#xff1a;审计日志、会话持久化与权限分级 1. 为什么企业需要更“稳”的AI对话平台 你有没有遇到过这样的情况&#xff1a;客服团队用AI助手回复客户&#xff0c;结果某天发现几条敏感对话被误删了&#xff1b;或者销售部门反馈&#xf…

作者头像 李华
网站建设 2026/4/23 10:48:42

RexUniNLU开源模型实战:基于arXiv:2304.14770论文的中文复现全流程

RexUniNLU开源模型实战&#xff1a;基于arXiv:2304.14770论文的中文复现全流程 1. 为什么你需要关注这个中文NLU模型 你有没有遇到过这样的问题&#xff1a;手头有个新任务&#xff0c;比如从电商评论里抽产品属性和对应情感&#xff0c;或者从新闻稿里识别事件和参与者&…

作者头像 李华
网站建设 2026/4/23 12:18:44

VisualCppRedist AIO 运行库维护工具技术白皮书

VisualCppRedist AIO 运行库维护工具技术白皮书 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 一、问题诊断&#xff1a;运行库故障识别与分析 1.1 如何判断运…

作者头像 李华