01o00o10

第一个开源自研servlet容器--灵童

成熟容器已经很多,为什么还要做灵童

项目开源地址:https://github.com/01o00o10/lingtong

先承认一个不太适合宣传的事实

Tomcat 已经成熟,Servlet 规范也不是新技术。今天重新开发一个 Servlet 容器,默认答案不应该是
“因为我们也能写”,而应该先接受质疑:为什么不直接使用现有实现?谁来承担 HTTP 协议漏洞、
Servlet 兼容差异、慢客户端、内存失控和长期维护?如果不能回答这些问题,自研容器只会把一个
稳定依赖变成一组新的生产风险。

灵童(LingTong)的出发点不是证明成熟容器过时,也不是把“国产”“高性能”“自主可控”几个词
放在一起就算完成设计。它试图解决一个更具体的问题:当团队希望在不修改标准 Servlet 和
Spring Boot 2.7 业务代码的前提下,真正掌握从 Socket、HTTP 状态机、背压、Servlet 生命周期到
诊断链路的边界时,是否可以构建一套可读、可测、可演进的运行时?

这个问题有工程价值,也有很高的失败概率。正因为如此,项目需要的不是围观一个“大工程”,
而是更多人来拆解并验证一批边界清晰的小问题。本文写给想理解或参与灵童的人:它现在能解决
什么,为什么值得做,哪些部分还不能相信,以及你可以从哪里开始。

一个 Servlet 容器到底替应用承担了什么

业务开发者写下 @GetMappingHttpServlet#service 时,实际把大量责任交给了容器。一次普通
请求至少穿过连接管理、报文解析、协议安全、线程调度、Servlet 映射、Filter、Session、异步
完成和网络写回。任何一层的边界错误,都可能在业务侧表现成同一个 404、超时或连接重置。

flowchart LR U[业务 Controller / Servlet] --> S[Servlet 语义] S --> D[路由 Filter Session Async] D --> E[应用执行域与背压] E --> P[HTTP/1.1 HTTP/2 WebSocket] P --> T[NIO TLS Socket] C1[业务希望<br/>代码不修改] -.约束.-> U C2[规范兼容] -.约束.-> S C3[有界资源] -.约束.-> E C4[协议与安全] -.约束.-> P C5[可诊断故障] -.贯穿.-> D C5 -.贯穿.-> E C5 -.贯穿.-> P

因此,“替换 Tomcat”不能只解释成 Maven 依赖树里少了一个 artifact。真正的替换至少包含三层:

  1. 入口替换:Spring Boot 能发现新的 ServletWebServerFactory,标准 Controller 可以启动;
  2. 行为替换:路由、参数、Cookie、Session、Filter、Listener、异步和错误派发符合目标应用
    实际依赖的语义;
  3. 运行替换:过载、超时、连接关闭、升级、停机排空和诊断可以被运维团队理解并控制。

灵童目前已经打通第一层和第二层的主要路径,也实现了第三层的一批基础机制,但没有完成完整
TCK 和所有真实应用迁移,所以仍是早期研发版本。

为什么值得重新做,而不是只包装现有容器

第一,掌握故障边界,而不只是掌握配置项

使用成熟容器时,团队通常通过 connector、线程池和超时参数间接控制运行时。对大多数项目这
已经足够。灵童面向的是另一类需求:团队希望能够直接定义连接属于哪个线程、工作队列满时返回
什么、请求体邮箱什么时候暂停读、响应 Future 完成究竟代表入队还是 Socket 写尽。

当前
NioHttpServer.Connection#dispatch
把请求从所属 Selector 分片提交到有界工作域;业务完成后,再通过分片命令队列回到原连接。
这种设计不一定天然更快,但所有权可以从代码直接核验,容量耗尽也必须选择暂停、拒绝或关闭,
不能退回无界堆积。

第二,把协议、执行和 Servlet 语义作为同一个系统验证

很多故障跨越模块边界。HTTP/1 请求体还没读完时应用提前响应,连接还能不能复用?HTTP/2 流
窗口耗尽时,Servlet 响应邮箱应该阻塞、暂停还是失败?异步 Servlet 超时与客户端断开同时发生,
谁只能完成一次?这些问题不能只靠一个解析器单元测试或一个 Controller 集成测试回答。

灵童选择自研网络和 HTTP 栈,是为了让这些边界能在同一个对象所有权模型里收敛。代价同样直接:
项目必须持续跟踪 RFC、安全问题和规范测试,不能把“代码都在自己仓库”误当作正确性证明。

第三,为嵌入式迁移建立一条可审计的最短路径

第一阶段目标不是复制完整应用服务器,而是 Java 8、javax.servlet、Spring Boot 2.7 的嵌入式
应用。公开 example 明确排除 Tomcat,引入灵童适配器,仍使用普通 @SpringBootApplication
@GetMapping。这个范围足够具体,能够让团队拿真实应用做 A/B 验收;它也足够克制,不会把
尚未实现的 JSP、完整 Web Profile 或商业认证藏进“兼容”两个字中。

第四,形成一套能被读懂的中间件源码

运行时项目的价值不只在最终二进制,也在问题分解方式。一个贡献者可以沿着
SpringApplication.run -> prepare -> start -> accept -> parse -> worker -> Servlet -> write
追踪完整链路,并在每个线程切换点找到有界队列和失败策略。源码仍然复杂,但目标是让复杂性来自
协议和生命周期本身,而不是来自隐含的全局状态。

posted on 2026-09-21 21:25  01o00o10  阅读(3)  评论(0)    收藏  举报

导航