记一次clawpanel空白页修复:从前端报错到后端定位

背景

给 fnOS 上的 OpenClaw 部署了一个 clawpanel Web 管理面板,访问 http://192.168.1.100:3080 时页面一片空白,Chrome 控制台报错:

POST http://192.168.1.100:3080/__api/health 404 (Not Found)

页面加载失败,一片白。今天记录一下完整排查和修复过程。

排查过程

Step 1:先看控制台报什么错

F12 打开开发者工具,刷新页面,看到请求 `POST /__api/health` 返回 404。

404 说明后端没有这个接口,前端收到错误后可能直接渲染白屏。

Step 2:检查端口和进程

ss -tlnp | grep 3080
# LISTEN 0 511 0.0.0.0:3080 users:(("node",pid=946337))

ps aux | grep 946337
# node server.js  (在 /vol1/@apphome/.../clawpanel-web/)

端口 3080 上跑的是 node server.js,是 clawpanel-web 的静态文件服务。

Step 3:看 server.js 的路由定义

grep -n "__api" server.js
# GET  /__api/openclaw/config
# PUT  /__api/openclaw/config
# POST /__api/gateway/reload
# POST /__api/openclaw/config/bind-all
# GET  /__api/services/status
# GET  /__api/openclaw/agents
# GET  /__api/openclaw/status/summary
# GET  /__api/version

找遍了,没有 `/__api/health`。

所有未匹配的 `/__api/` 请求统一走到:

res.writeHead(404); res.end('{}');

这就是根本原因。

修复方案

给 server.js 加一个 `/__api/health` 端点,负责检查 OpenClaw Gateway 是否可达。

if (req.method === 'GET' && url.pathname === '/__api/health') {
  const cfg = readConfig();
  const gatewayHost = cfg?.gateway?.host || '127.0.0.1';
  const gatewayPort = cfg?.gateway?.port || 13550;

  const probe = http.request({
    hostname: gatewayHost,
    port: gatewayPort,
    path: '/__api/health',
    method: 'GET',
    timeout: 3000
  }, (pr) => {
    let data = '';
    pr.on('data', c => data += c);
    pr.on('end', () => {
      try {
        const j = JSON.parse(data);
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.end(JSON.stringify({ok: true, gateway: j}));
      } catch {
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.end(JSON.stringify({ok: true, gateway: {status: 'unknown'}}));
      }
    });
  });

  let responded = false;
  const respond = (ok, extra) => {
    if (responded) return;
    responded = true;
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(JSON.stringify({ok, ...extra}));
  };

  probe.on('error', () => respond(false, {error: 'gateway unreachable'}));
  probe.on('timeout', () => { probe.destroy(); respond(false, {error: 'gateway timeout'}); });
  return;
}

要点说明:

  • responded 守卫:防止多个回调(error + end)同时触发导致 ERR_HTTP_HEADERS_SENT 崩溃
  • 同时支持 GET 和 POST:前端用的是 POST,所以两个 method 都加上
  • 超时处理:3秒超时,防止 gateway 挂了时一直卡着

结果

curl -X POST http://127.0.0.1:3080/__api/health
# {"ok":false,"error":"gateway timeout"}

curl -X GET http://127.0.0.1:3080/__api/health
# {"ok":false,"error":"gateway timeout"}

返回 200 + JSON(不再是 404),页面正常加载。

gateway timeout 是因为 OpenClaw Gateway 本身没有 `/__api/health` 端点,但这不影响页面显示——前端收到 ok: false 后会做相应处理。

经验总结

  • 控制台是第一信息源:404 错误直接指向”这个接口不存在”,不要猜
  • 先定位进程再看代码:知道是哪个进程在跑,代码路径就清晰了
  • Node.js HTTP 双写错误:error + end 同时触发是最常见坑,用守卫变量解决
  • 健康检查端点:前后端分离的项目,前端往往会轮询一个 health 接口,最好一开始就约定好

发表评论