← 文章 / Writing

Engineering8 min编者 nabunana ↗

Java 没有断,WebSocket 为什么一直重连

一次公网部署故障复盘:REST 正常、进程没重启,WebSocket 却持续 403,子路由刷新也跟着 404。
#Java#WebSocket#Nginx#Vue#部署

Three Body Lab 部署到公网后,首页能打开,REST 接口也正常。可一点击开始实验,页面就一直显示“正在重连”。实验详情页第一次能进去,刷新又变成 Spring 的 Whitelabel 404。

一开始很容易怀疑 Java 服务扛不住实时推送,或者服务器资源不够。实际检查下来,Java 进程没有退出,systemd 的 NRestarts 是 0,也没有 OOM。实验接口持续返回成功,只有 NGINX 日志里的 WebSocket 握手一直是 403。

两个现象看起来都像“服务断了”,原因其实分开。

端口在代理时丢了

站点跑在公网 8721 端口。浏览器发起 WebSocket 时,Origin 是:

http://39.108.101.149:8721

原来的 NGINX 配置使用:

proxy_set_header Host $host;

$host 没把这个非标准端口继续传给后端。Spring 收到的 Host 不带 :8721,与浏览器 Origin 对不上,同源校验直接拒绝握手。前端只知道连接失败,于是按退避策略继续重连,看起来很像 Java 服务反复断开。

我用同一个实验和同一个 Origin 做了两次握手。Host 不带端口时返回 403,带上 :8721 后返回 101。对照结果出来后,就没必要改模拟算法、降低发布频率或扩容了。

修复时把 WebSocket 路径单独列出来,Host 改用保留端口的 $http_host,并补齐转发 Host、端口和协议头。关键配置是:

location /ws/ {
    proxy_pass http://127.0.0.1:18721;
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $threebody_connection_upgrade;
}

刷新 404 是另一条链路

前端使用 Vue History 路由。浏览器在应用内进入 /experiments/{id} 时,由前端路由接管,不会找后端。刷新以后,请求直接落到 NGINX 和 Spring;服务器没有这个页面映射,就返回 Whitelabel 404。

如果把所有 404 都回退到 index.html,页面刷新是好了,但不存在的 API 和静态资源也可能返回一份 HTML,排错会更麻烦。

实际配置把路由分开处理:/api//assets/ 保留真实 404,/ws/ 走升级代理,普通前端路径才回退到 Vue 入口。实验页和报告页可以直接访问,错误的接口地址仍然会明确报错。

上线时没有重启 Java

这次改动只在部署层。服务器上的旧 NGINX 配置先备份,仓库里保存同一份正式配置。上传后比较 SHA-256,执行 nginx -t,通过才平滑 reload。Java 进程和实验数据都没动。

验收没有停在“首页 200”。我创建了一次临时实验,用浏览器同源 Origin 建立公网 WebSocket,确认状态变成 Open,并实际收到一条 SNAPSHOT。NGINX access log 里记录了 101;实验页和报告页直接请求都是 200;不存在的 API 与静态资源仍是 404。测试实验随后取消并删除。

之前的部署检查只看了根页面和 REST,还确认过配置里写了 Upgrade,于是误以为 WebSocket 已经可用。以后这类站点的验收项得拆开:HTTP 页面、REST、WebSocket 101、至少一条业务消息、前端子路由刷新。少一个,都只能说明一部分链路活着。

今聴いている / NOW LISTENINGYorushika

读完之后,留一点安静给音乐。这是一则状态记录,不是播放器。

站内搜索

AgentVibe的东西 真的看懂了吗Notes · 工程 反思 AI 能力 · 攒了一堆能跑的项目,能力却没长进。问题不在代码谁写的,在于我有没有真正想明白。谈谈多Agent并发与OpenclawNotes · AI 工作流 OpenClaw 工程取舍 · 把一个写文案的活拆成五六个 Agent 开会,听着爽,实际是 Token 火葬场。真正把事做完的,是单 Agent 加工具,加上几个克制的角色。一次 GitHub Actions 自动发布复盘Engineering · Astro GitHub Actions Nginx SSH CI/CD 部署 · 从旧 Hexo GitHub Pages、Astro 新站到服务器:怎样处理分支切换、SSH 信任、未入库的 94 首音乐,以及可回滚的原子发布.更新静态博客时,我不直接覆盖 /var/www/blogEngineering · Astro Nginx SFTP 部署 静态网站 · 这个 Astro 博客的发布过程:先在旁边准备好完整站点,核对资源,再一次替换并保留回滚。Java 没有断,WebSocket 为什么一直重连Engineering · Java WebSocket Nginx Vue 部署 · 一次公网部署故障复盘:REST 正常、进程没重启,WebSocket 却持续 403,子路由刷新也跟着 404。做“今天吃什么”,我只想让页面给一家店Engineering · Java Vue uni-app 推荐系统 产品设计 · ELMA 目前的项目思路:少问几个问题,一次给一家店,再从反馈和行为里慢慢学。做一个能按真实尺寸打印的卡牌 PDF 工具Projects · Python Pillow PDF Tkinter Desktop · 卡牌图片放进 A4 不难,麻烦的是毫米、DPI、图片比例和桌面程序里的几个小坑。三体模拟器跑起来以后,四条链路开始互相拖后腿Engineering · Java Vue WebSocket Canvas 性能优化 · 一次实时模拟项目里的拆分:积分照常跑,网络只发该发的,画布和归档各自守住上限。聊天摘要 Agent:消息拉全以后,才轮得到模型Agent · Python Agent SQLite Privacy 容错设计 · 记录聊天摘要从能运行到可以放心使用,中间补过的分页、游标、证据和隐私边界。拿一个 8KB 数组写个 mallocSystems · C Memory Data Structure 嵌入式 · MiniMalloc 里真正需要想明白的几件事:块头放哪、空闲块怎么找,以及 free 后怎样把内存拼回去。我为什么还在用 RSSNotes · RSS Reading Web · 不猜兴趣,也不替我调整顺序。想看的来源,自己订阅就够了。这个夏天看过的动画ACG · Anime ACG Life · 没做排名,只记下几段过了一阵子还会想起来的内容。ProjectsPage · 正在构建的项目与实验MusicPage · Yorushika、n-buna、Vocaloid 与听歌记录AboutPage · 关于这个数字空间和它的主人