Skip to content

使用webhook,长时间后会掉线,接口不可达 #48

Description

@kukume

未知原因,没有日志,长时间运行后,webhook的端口,本地访问也不可达。

下面是ai分析的,不清楚原因。

这个现象把范围缩得很死了:

不是路径错(那会 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 都没有  

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions