对应实践:
course/practice/labs/lab10-step9/
主要修改文件:course/practice/labs/lab10-step9/framework/student.c
验证命令:make clean && make test
到上一章为止,课程已经把“多轮对话”里的字符串协议拆清楚了。
你已经知道 prompt 怎样组织,回复又该怎样从模型输出里截出来。
但只停在命令行程序里,还谈不上真正的服务化。
一旦你想让前端、脚本或别的程序来调这个模型,就需要把 chat 能力封装成一个可通信的接口。
在当前课程阶段,这个接口最自然的形态就是 HTTP。
需要特别说明的是:
这一章并不要求学员自己从头写 socket 服务器。课程把范围压得很小,只让你实现 HTTP 协议层两个最小积木:
- 解析请求行;
- 组装响应帧。
也就是说,Chapter 10 的核心问题不是“网络编程有多复杂”,而是:
当一个模型能力要被外部程序调用时,最基础的请求/响应协议长什么样?
10.1 为什么服务化首先是协议问题
初学者第一次接触“把模型做成服务”时,很容易把注意力都放在端口、线程、监听循环上。
这些当然都重要,但它们还不是服务化的第一层边界。
第一层边界其实是协议:
- 客户端发来什么文本;
- 服务端如何把它拆成 method / path / body;
- 服务端回去的内容该怎样拼成合法 HTTP 响应。
如果这一层都没弄清楚,那么就算 socket 已经连上,客户端也不知道你在说什么。
所以本章故意把实践缩到两道协议题里,是为了让学员先掌握:
- 请求帧的最小结构;
- 响应帧的最小结构。
10.2 本章你要建立哪些判断
本章结束后,你应当能够清楚说出:
- 一个 HTTP 请求行为什么通常长成
METHOD PATH HTTP/1.1。 - 为什么 headers 和 body 之间必须有一个空行。
Content-Length为什么是必须的,而不是“可选信息”。- 为什么响应行的
\r\n不是普通换行细节,而是协议要求。 - 为什么 OpenAI 兼容接口的价值,本质上是“客户端和服务端共享了一套 JSON 契约”。
这些判断一旦建立起来,后面真正去跑 curl 或前端调用时,你就不会只把服务端当黑盒。
10.3 先看 practice target:这章改哪里
本章实践目录是:
course/practice/labs/lab10-step9/
├── TASK.md
├── Makefile
└── framework/
├── student.c <- 主要修改这里
├── student.h
├── verify.c <- 自动验证,不改
└── verify.h
当前需要你实现的函数有两个:
student_http_parse_request_linestudent_http_build_response
这个切法非常合理,因为它刚好对应 HTTP 协议里最基础的两个动作:
- 从客户端来的第一行里拆出“请求意图”;
- 把服务端结果重新打包成客户端能理解的响应文本。
10.4 为什么请求行解析是“结构恢复”
请求行看起来只是一行字符串,例如:
GET /health HTTP/1.1
但对服务器来说,它不是普通文本,而是一段被压平的结构数据。
解析请求行做的事情,本质上是在把这段压平字符串重新恢复成三个字段:
- method
- path
- version
这跟前一章“从模型原始输出里截出 assistant 回复”的思路其实非常像。
两者都是在做一件事:从一段线性文本里恢复协议结构。
10.5 为什么响应构建是“主动声明边界”
响应构建和请求解析正好反过来。
这里服务器不是在恢复结构,而是在主动声明结构。
你要明确告诉客户端:
- 这是
HTTP/1.1响应; - 状态码是多少;
- 内容类型是什么;
- body 有多长;
- headers 到哪里结束,body 从哪里开始。
这里最不能忽略的就是 Content-Length。
因为 HTTP 是字节流协议,客户端如果不知道 body 的长度,就无法稳妥判断响应到底什么时候结束。
所以本章最应该建立的不是“能拼出一串文本”,而是:
一次合法响应,必须把自己的边界写清楚。
10.6 为什么课程现在只做协议骨架,不让你写完整服务
如果在这一章同时让学员:
- 管 socket;
- 管请求解析;
- 管 JSON;
- 管模型调用;
- 管并发或阻塞行为;
那教学负担会一下子跳得非常高。
课程当前的选择更务实:
先把“协议骨架”单独拿出来练。
因为只要请求行解析和响应组装是清楚的,后面的完整服务化路径就已经有了坚实基础。
反过来,如果这两层还混乱,直接跑完整 server 只会让错误来源更加难找。
10.7 本章实践步骤
task 10.1:先读 student.c 和 verify.c
进入:
cd course/practice/labs/lab10-step9
建议先读:
framework/student.hframework/student.cframework/verify.c
当前验证器主要检查:
GET /health HTTP/1.1能否被正确拆成三段;POST /v1/chat/completions HTTP/1.1能否被正确拆成三段;- 非法请求行是否会被拒绝;
- 200 OK 响应是否包含正确的 header 与 body;
- 404 Not Found 响应是否仍然是合法 HTTP 帧。
task 10.2:实现 student_http_parse_request_line
这一步建议你始终记住目标不是“做复杂解析器”,而是从最小协议格式里拿出三段字段。
实现时最值得关注的是:
- 找两个空格;
- 段长不要越界;
- 任何结构异常都应该返回失败,而不是悄悄截断。
这是一个很典型的协议解析习惯:
宁可明确报错,也不要默默吞掉坏输入。
task 10.3:实现 student_http_build_response
这一题最值得在脑子里保持一张“响应骨架图”:
status line
headers
空行
body
只要这张骨架没有乱,你再逐项补:
Content-TypeContent-LengthConnection: close
代码就不容易写偏。
task 10.4:运行当前真实基线
在还没补完两个函数前,先执行:
make clean && make test
当前 Lab10 的真实初始状态是:
- 1 通过,4 失败。
已经通过的那一项通常只是非法请求行失败这类保底路径。
真正与协议骨架直接相关的部分,例如:
- 请求行拆分;
- 200/404 响应组装;
目前都还没有完成。
这说明当前 lab 已经能运行,但 HTTP 协议层的核心责任仍然留给你自己补完。
task 10.5:完成后重新验证
当你补完两个函数后,再运行:
make clean && make test
如果实现正确,你应当看到:
- 常见请求行都能被稳定解析;
- 非法输入被拒绝;
- 响应文本包含合法头部、空行和 body。
到这里,chat 系统就已经具备了向外暴露接口的最小协议层。
10.8 常见错误与排查顺序
最常见的错误通常是:
- 请求行没有正确按两个空格切开;
- 缓冲区长度检查不严;
- 响应行用了
\n而不是\r\n; Content-Length算错;- headers 和 body 之间忘了空行。
建议排查顺序是:
- 先看请求行三段是否拆对;
- 再看响应状态行;
- 最后看 header/body 边界是否正确。
10.9 思考题
- 为什么
Content-Length对 HTTP/1.1 响应如此关键? - 为什么协议里要求
\r\n,而不是普通的\n? - 为什么说 OpenAI 兼容接口的价值,本质上是一种共享协议而不是某个具体模型实现?
10.10 本章小结
Chapter 10 把课程从“命令行里的模型能力”推进到了“可被外部程序调用的模型能力”。
更重要的是,它让学员第一次明确看到:
服务化的第一层不是 socket 技巧,而是协议边界。
下一章继续往前走时,这层边界就会变得非常有用。因为一旦模型开始被频繁调用,推理效率问题就会立刻浮现出来,而 KV cache 正是为那个问题准备的。