这个现象把范围缩得很死了:
不是路径错(那会 404),而是本机这个端口上的 HTTP 服务已经不接请求了。
进程还活着、业务日志也可能安静——符合这个 SDK 的实现方式。
---
现象含义
有 HTTP 404
• 说明: 服务还在,只是 path 不对
有 200/500/连接重置
• 说明: 至少 accept 还在
**浏览器“打不开”(连不上/一直转圈)**
• 说明: listen 线程挂了、线程池耗尽、或 accept 不再处理
所以“跑久了 webhook 掉线、程序无异常”≈:
HttpServer 半死/卡死 → QQ 平台推不过来 → 你也 curl/浏览器不进 → 无业务异常可抛。
---
根因(基本可定在 HookAuth)
// HookAuth.webhookServerStart()
httpServer.createContext(..., exchange -> {
...
String resp = null;
// 验签异常时 resp 仍是 null
// pack 解析失败时 pack.getOp() 也会 NPE
exchange.sendResponseHeaders(200, resp.length()); // NPE
exchange.getResponseBody().write(...);
// ❌ 从未 exchange.close()
});
// ❌ 未 setExecutor
// ❌ 异常无 finally
1. 不 close()(主因)
com.sun.net.httpserver.HttpExchange 必须在处理结束时 close()。
这里写完 body 就结束,连接/exchange 资源会长期挂着。
跑久了常见结果:
- worker 线程占满 / 连接堆积
- 新请求进不来(浏览器像“打不开”)
- 进程不崩、无你期望的业务异常
2. resp == null → NPE(加速卡死)
验签异常时 resp 仍是 null,下一行 resp.length() 直接 NPE。
NPE 在 HttpServer 回调里:
- 不一定进你清晰的业务栈
- exchange 更不会 close
- 该连接半开,再叠加 1,时间一长整站挂掉
浏览器 GET / 或 GET /webhook0 也可能踩到:body 空 → JSON 解析失败 / pack 为 null → 同样 NPE。
3. 无自愈
Webhook 模式 没有 WS 那套 heartbeat / onClose / reconnect。
HttpServer 卡死后不会自动重建,只能重启进程。
---
和“无异常”的关系
这里不是“连接断了抛 IOException”,而是:
1. handler 偶发 NPE / 资源泄漏
2. HttpServer 内部慢慢不可用
3. 外层 Starter 觉得 bot 还在跑
4. 你看到:进程在、无异常、localhost 打不开、平台也不再推
---
掉线时建议立刻看一眼(30 秒)
在已经打不开的那台机器上:
# 1) 端口是否还在听
ss -lntp | grep <你的webhook端口>
# 2) 是否大量半开连接
ss -ant | grep <端口> | awk '{print $1}' | sort | uniq -c
# 3) 进程是否还活着
ps -ef | grep -i java
解读:
- LISTEN 没了 → 服务停了 / 进程挂了(少见,你描述更像还活着)
- LISTEN 在,但连接不上 + CLOSE_WAIT/ESTAB 很多 → 高度符合 不 close + 线程/连接耗尽
- jstack 里大量 HttpServer / Dispatcher / 卡在 handler → 同上
---
修复要点(确认根因后改这里就够)
只改 HookAuth.webhookServerStart() 即可,最小集:
1. try/finally { exchange.close(); }(必须)
2. resp 默认 ""/"{}",禁止 null
3. pack == null、body 非法 JSON 时直接 400,别 NPE
4. httpServer.setExecutor(固定线程池)
5. 写响应用 byte[] 长度;验证接口 Content-Type: application/json
6. 可选:加 /health 返回 ok,方便区分“端口死了”还是“业务 path 挂了”
结构上应是:
try {
读 body / 解析 / op13 或验签 / 分发
组 resp + status
} catch (e) {
log; status=500; resp="{}"
} finally {
写响应(再包一层 try)
exchange.close() // 无论如何执行
}
---
结论
你的新证据说明问题已经不在“QQ webhook 协议玄学”,而在:
本库 Webhook 用的 com.sun.net.httpserver 实现不完整:不 close + 空 resp NPE → 长跑后 HTTP 层静默卡死。
这能同时解释:
- 长时间才出问题
- 程序无明显异常
- localhost 连 404 都没有
未知原因,没有日志,长时间运行后,webhook的端口,本地访问也不可达。
下面是ai分析的,不清楚原因。