# Step 66: HTTP + SSE 后端

一句话导读:把 QueryEngine.run() 的 async generator 事件流直接映射成 SSE,证明同一个无头内核可以换一张 Web 前端。


# 一、这一步做了什么(What)

step66 新增 server/session.ts 和 server/httpServer.ts。createSession() 组装一个可运行的 agent 会话;HTTP 服务提供 POST /chat,收到用户消息后跑 engine.run(),把每个 EngineEvent 序列化成 SSE data: 推回客户端。

它是 Phase 7 Part B 的起点:不继续深挖手写 Ink,而是走真实 Claude Code 的多前端路线。

# 二、面试官视角:为什么要做?(Why)

面试题:已经有终端 REPL 了,为什么还要把 agent 包成 HTTP 服务?

因为真实 Claude Code 本质是“无头内核 + 多个 surface”。终端只是一个 surface,Web、SDK、IDE、Bridge 都可以消费同一条事件流。只要 QueryEngine 不绑定终端,换前端就是工程装配问题。

终端 REPL HTTP/SSE
readline 输入 POST JSON
console 渲染事件 SSE 推事件
本地交互 浏览器/远端可消费

# 三、原理:它是怎么工作的(How)

SSE 处理在 server/httpServer.ts:

res.writeHead(200, {
  "Content-Type": "text/event-stream; charset=utf-8",
  "Cache-Control": "no-cache",
  Connection: "keep-alive",
});

for await (const ev of session.engine.run(message)) {
  res.write(`data: ${JSON.stringify(ev)}\n\n`);
}
res.write(`data: ${JSON.stringify({ type: "done" })}\n\n`);
1
2
3
4
5
6
7
8
9
10

请求体中文坑的修复也很关键:

const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk as Buffer);
const body = Buffer.concat(chunks).toString("utf8");
1
2
3

数据流:

浏览器 POST /chat
  → createSession().engine.run(message)
  → thinking/text/tool/round 事件
  → SSE data: {...}
  → 浏览器逐条渲染
1
2
3
4
5

# 四、深入追问(面试常见 follow-up)

Q:为什么用 SSE,不直接返回完整 JSON?
A:agent 输出是流式的,工具调用和 thinking 也会分阶段出现。SSE 能边生成边推,和 async generator 天然匹配。

Q:SSE 的限制是什么?
A:单向。服务器能推给浏览器,但浏览器不能在同一通道里回答权限请求,所以 step67 要换 WebSocket。

Q:为什么请求体要先 Buffer.concat 再 utf8 解码?
A:中文可能跨 chunk。边拼字符串会造成多字节字符拆坏,先收 Buffer 再统一解码才安全。

Q:这一步权限怎么处理?
A:step66 暂时非交互:危险命令仍由 Bash 分类器兜底,其它自动放行。真正交互式权限留给双向通道。

# 五、踩坑 / 设计权衡

这一步故意用 Node 原生 http,不是为了生产,而是为了看清协议本身:HTTP 请求进来,SSE 长连接出去,中间就是 for await 消费引擎事件。

# 六、与真实源码的对照

我们的实现 Claude Code 源码
engine.run() → SSE SDK/Bridge 消费 async 事件流
单会话 HTTP 服务 多会话、鉴权、远程 session
非交互权限兜底 完整 canUseTool 控制通道
无取消/背压 abort、断线、重连、状态同步

# 七、一句话总结

step66 的关键不是 HTTP 本身,而是证明 QueryEngine 已经足够无头:同一条事件流可以被终端消费,也可以被 Web 通过 SSE 消费。

# 下一节预告

下一篇是 step67:用 WebSocket 补上双向通道,让浏览器也能处理交互式权限。