从零实现 C++ Linux WebServer:从最小服务器到 HTTP/1.1 解析
这篇文章记录我从零实现一个 Linux C++ WebServer 的过程。项目从最小可运行服务器开始,逐步加入 HTTP/1.1 请求解析、响应构造和静态文件返回能力。
这个阶段的目标不是高性能,也不是完整复刻 TinyWebServer,而是先把基础链路和 HTTP 处理流程真正写明白。
第一阶段:最小 HTTP 服务器
最开始的目标很简单:
1 | 启动服务器:./server 8080 |
这一版虽然功能很小,但它跑通了 Linux 网络服务器最基础的流程:
1 | socket -> bind -> listen -> accept -> recv -> send -> close |
完成这个阶段后,我理解了几件事:
- HTTP 响应不能只有正文,还必须有状态行、响应头、空行和响应体。
send不一定一次发送完,应该根据返回值循环发送。accept得到的client_fd在请求处理结束后必须关闭。- 编译生成的
server、main等二进制文件不应该提交到 Git。
第二阶段:静态页面和 HTTP 解析
固定返回 Hello World 之后,下一步是让服务器根据请求内容返回不同结果。
为此我添加了两个方向的内容:
一是静态页面目录:
1 | static/ |
二是 HTTP/1.1 解析模块:
1 | http-1.1/ |
其中 HttpRequest 用于保存请求信息:
- method
- path
- version
- header
- query
- body
HttpResponse 用于根据状态码、Header 和 body 构造完整 HTTP 响应。
这次实现中遇到的问题
1. 调试输出容易污染服务器行为
在实现请求解析时,我加了很多 cout 输出 method、path、header 等中间结果。
这对调试有帮助,但放在服务器主流程里会让终端输出变得很乱,也不利于后续测试。因此后面把这些调试输出删掉了。
更合理的做法是后续实现日志系统,用日志级别区分:
- debug
- info
- warn
- error
2. 请求解析要区分“没读完整”和“真的错误”
HTTP 请求从 socket 读出来时,不一定一次 recv 就完整。
所以解析结果不能只看错误类型,还要区分解析状态:
- Completed:请求完整并解析成功。
- Incompleted:请求还没读完整。
- Error:请求格式确实错误。
当前代码已经有 ParseStatus,但主流程还需要更明确地使用它。
3. URL 路径和本地文件路径不能直接等同
客户端请求的是 URL:
1 | GET /index.html HTTP/1.1 |
服务器真正读取的是本地文件:
1 | static/index.html |
这中间需要一个明确的映射步骤。
同时还要避免目录穿越,例如:
1 | GET /../README.md HTTP/1.1 |
学习阶段可以先用简单规则处理:
/映射到static/index.html。/index.html映射到static/index.html。- 包含
..的路径直接返回400 Bad Request。 - 文件不存在返回
404 Not Found。
4. Header 解析要注意下标来源
解析 Header 时,我把某一行切成了 line,但后续取 value 时仍然混用了原始 raw 的下标。
这类 bug 很常见:一旦字符串被切片,后续下标到底是相对于原始字符串,还是相对于当前行,必须统一。
更稳的思路是:
1 | 先拿到一整行 header line |
当前项目状态
当前服务器已经具备雏形:
- 能监听端口。
- 能接收 HTTP 请求。
- 能初步解析 HTTP/1.1 请求。
- 能构造 HTTP 响应。
- 已经有静态错误页面。
但还不是一个稳定的静态文件服务器。
接下来需要先修复这些问题:
- 静态文件路径构造。
- query 解析。
- Header value 截取。
- body 长度判断。
ParseStatus与ParseErrorType的处理边界。- Makefile 中不应把
.hpp当作编译单元。
下一步计划
下一步目标是完成一个稳定的阻塞式静态文件服务器:
1 | GET / HTTP/1.1 -> 200 OK + static/index.html |
完成这个阶段之后,再参考 TinyWebServer 的路线,进入:
HttpConnection连接处理模块。- 非阻塞 socket。
epoll。- 定时器。
- 日志。
- 线程池。
目前先不急着做并发。先把请求解析、文件映射和响应构造写稳,后面的 epoll 和线程池才有清楚的落点。
