Skip to content

UVHTTP 设计哲学 ​

这份文档阐述 UVHTTP 的核心设计哲学——它是什么、不是什么、以及为什么这样设计。 所有决策和代码评审都应以此为准绳。


一、项目定位:一个组件,不是一个框架 ​

UVHTTP 是一个 C 语言 HTTP/1.1 + WebSocket 服务器库(library),不是一个应用服务器(如 nginx、Apache),也不是一个框架(如 Express、Flask)。

这个定位意味着什么:

项目是…项目不是…
一个可嵌入的 C 库一个独立运行的守护进程
一个协议处理组件一个业务逻辑容器
你应用的一部分你应用的全部

推论: UVHTTP 不提供认证、授权、数据库、模板引擎、会话管理、配置热加载等功能。这些是业务层的事。UVHTTP 只做一件事——正确地处理 HTTP 和 WebSocket 协议——并把它做好。


二、分发模型:源码分发,用户编译 ​

UVHTTP 不提供二进制分发(不发布 .deb、.rpm、预编译静态库)。用户通过以下方式使用:

bash
git clone --recurse-submodules https://github.com/adam-ikari/uvhttp.git
cd uvhttp && mkdir build && cd build
cmake .. && make

这决定了什么 ​

1. 版本控制 = API 版本控制,不是 ABI 版本控制

因为用户总是从源码编译并与自己的应用静态链接,ABI 兼容性不是承诺。我们的版本号(SemVer)承诺的是源码级 API 兼容性:

MAJOR.minor.patch
  │      │      └── 不破坏 API 的修复(bug 修复、测试、文档)
  │      └──────── 不破坏 API 的新功能(新函数、新模块)
  └─────────────── 破坏 API 的变更(函数签名变更、删除、行为不兼容)
  • PATCH 升级:完全向后兼容,无需修改用户代码
  • MINOR 升级:向后兼容,新功能可选,现有代码不变
  • MAJOR 升级:API 不兼容,用户需要修改代码

2. 编译时裁剪是核心体验

用户通过 CMake 选项选择他们需要的功能:

cmake
cmake .. -DBUILD_WITH_WEBSOCKET=OFF -DBUILD_WITH_COMPRESSION=OFF

未使用的功能不会编译,不增加代码体积。这是嵌入式优先的具体体现。

3. 依赖管理是用户的责任

UVHTTP 使用 git submodule 管理依赖(libuv、llhttp、mbedtls、xxhash、miniz)。用户只需 --recurse-submodules 一次性获取所有依赖。这与"零外部依赖"的哲学一致——没有系统包管理器的依赖。


三、嵌入式优先 ​

UVHTTP 的优化目标是嵌入式设备和长时间运行的服务,不是峰值吞吐量。

优先级排序 ​

内存安全 > 内存稳定性 > 二进制体积 > 启动时间 > 峰值吞吐量

具体指标 ​

指标目标衡量方式
内存泄漏0(ASan 证明)ASan CI 门禁,每夜运行
长时间运行 RSS不增长scripts/performance/long_run_memory.sh
静态库体积~257 KBRelease build, stripped
32-bit 支持必须CI 门禁 (-m32)
冷启动时间~310 ms进程启动到首字节
编译时裁剪按需付费CMake 特性开关

低资源使用优先 ​

UVHTTP 的"嵌入式优先"不仅指内存,还包括所有形式的资源。在资源受限设备上,CPU、内存、带宽、电池每一项都是宝贵的:

资源UVHTTP 的态度具体体现
内存 (RAM)最小化静态占用 + 零泄漏编译时裁剪、零全局变量、缓存行对齐
CPU零成本抽象、无中间层直接调用 libuv、编译期宏消除
带宽零拷贝路径原生 sendfile、避免不必要的缓冲区拷贝
存储小静态库~257 KB stripped 静态库
启动快速冷启动~310 ms 首字节

核心信念:在低资源场景下,每一个字节、每一个 CPU 周期都是可以衡量的成本。

UVHTTP 的选择都遵循这一信念:

  • 编译时裁剪——未用到的功能不编译,不占 ROM/RAM
  • 零全局变量——状态都在实例内,支持多实例也支持销毁释放
  • 零拷贝——sendfile 让大文件不经用户态缓冲直接到 socket
  • 统一分配器钩子——嵌入式用户可注入自己的内存池,掌控每一次分配
  • 32-bit 支持——指针减半,结构体随之缩小

与之相对:如果一个"优化"让代码更复杂、体积更大、依赖更多,即使它提升了一点峰值吞吐量,也违背了低资源优先的原则——除非它有实测数据证明整体资源占用下降。

这意味着什么 ​

  • 峰值吞吐量不是卖点(nginx 和 h2o 在这方面更强)
  • 每个连接泄漏 1 KB 是一个 bug——在嵌入式设备上,这会在 10 小时内耗尽内存
  • 32-bit 支持不是可选项——许多嵌入式设备仍是 32-bit
  • 编译时裁剪——用户只链接他们需要的功能,不浪费一个字节

四、内存安全即基础设施 ​

内存安全不是 UVHTTP 的一个"特性"——它是基础设施,和编译一样不可或缺。

三层保障 ​

层级机制频率
编译期ASan + UBSan CI 门禁每次 PR
每夜完整 ASan + UBSan 跑全部测试每夜 CI
运行时长运行 RSS 稳定性测试每夜 CI

原则 ​

  • ASan/UBSan 零发现是合并 PR 的前提条件,不是可选目标
  • no_sanitize("address") 不被允许——禁用 sanitizer 是最后的手段,且必须附带解释和 issue 跟踪
  • VALGRIND 辅助但不是替代——ASan 捕获的 bug 比 valgrind 更多,速度更快
  • 内存泄漏 = 功能 bug,与逻辑错误同级

五、测试即基础设施 ​

UVHTTP 的测试代码量(52K 行)是生产代码(21K 行)的 2.5 倍。这不是过度工程——这是基础设施。

测试哲学 ​

生产代码测试代码
21K 行 C52K 行 C++(gtest)
47 个文件128 个文件
实现功能证明功能正确

原则 ​

  • 测试不是附属品——测试代码与生产代码同级
  • 测试门禁——所有测试必须通过才能合并 PR
  • 测试分离——生产代码不含测试代码(无 #ifdef TEST)
  • 覆盖不是目标,正确性才是——但仍追求高覆盖率作为副作用
  • 新功能必须附带测试——没有测试的功能不存在

六、零成本抽象 ​

零成本抽象(Zero-Cost Abstraction)是 UVHTTP 的核心工程哲学:你为抽象付出的成本为零——抽象只是编译期的组织手段,不产生任何运行时开销。这一概念借鉴自 C++ 的 zero-overhead principle,但 UVHTTP 用 C 的方式实现:通过预处理宏与静态内联,让抽象在生成代码之前就被完全消除。

为什么这是核心哲学 ​

UVHTTP 面向嵌入式低资源场景(见第三节),一个为"写起来方便"而引入、却让每个请求多付出几次分支、几次间接调用或一层栈帧的抽象层,会直接侵蚀吞吐量、增大指令缓存压力、并扩大二进制体积。因此:

抽象是允许的,但抽象的开销必须为零——否则它不是抽象,是负债。

三种零成本抽象手段 ​

手段说明例子
编译期宏特性开关在预处理阶段完全消除未使用代码UVHTTP_FEATURE_* 系列开关、UVHTTP_LIKELY/UNLIKELY 分支预测
静态内联函数static inline 在优化构建中内联展开,无调用开销uvhttp_hash、uvhttp_method_to_string 快速路径
直接调用,无中间层直接调 libuv / llhttp / mbedtls,不包一层 wrapper 再转调uv_tcp_*、llhttp_execute 直接使用

验证方式 ​

零成本抽象不能靠"声称",要靠证据:

  • 反汇编检查:热路径不包含抽象层残留的函数调用
  • 基准对比:启用/禁用某个抽象层,吞吐量无差异(见 docs/PERFORMANCE_TARGETS.md)
  • 代码审查:性能关键路径上的抽象必须能被编译器完全消除

与低资源优先的关系 ​

零成本抽象是实现"低资源使用优先"(第三节)的手段,低资源使用优先是零成本抽象的目的,二者互为表里:

  • 零成本抽象 → CPU 与指令缓存效率
  • 低资源优先 → 内存、体积、启动时间的综合资源观

七、代码设计原则 ​

零全局变量 ​

所有状态通过 libuv 的 data 指针(loop->data、handle->data)传递。这使 UVHTTP 支持:

  • 多实例(同一进程多个服务器实例)
  • 单元测试(无全局状态污染)
  • 可测试性(所有状态可注入)

统一错误系统 ​

所有可能失败的函数返回 uvhttp_error_t——一个携带错误码、描述和恢复提示的统一错误类型。没有 -1、NULL 或 errno 作为错误信号。

缓存行对齐 ​

热路径结构体(uvhttp_request_t、uvhttp_response_t、uvhttp_connection_t)按缓存行(64 字节)显式对齐和填充。这不是微优化——这是对多核场景下缓存伪共享的防御。

编译期特性标记 ​

所有可选功能通过编译期宏控制(UVHTTP_FEATURE_COMPRESSION、UVHTTP_FEATURE_WEBSOCKET 等)。未开启的功能在预处理器阶段被完全移除,零运行时开销。


八、C 标准:C99 的取舍 ​

UVHTTP 选择 C99(-std=c99)作为最低标准,理由如下:

选的:

  • 可移植性(GCC 4.8+、Clang 3.3+ 均支持)
  • 嵌入式编译器支持(不少嵌入式工具链对 C11 支持不完整)
  • 最小惊异原则(C99 的行为是已知的)

不选的:

  • C11/C17 的可选特性(匿名结构体、泛型表达式)——增加编译器要求,收益有限
  • C23 的新特性——多数嵌入式编译器尚未支持

但实际构建使用 C11 标准(CMAKE_C_STANDARD 11)以利用 C11 的编译器特性,同时确保代码风格兼容 C99 编译器。


九、项目边界:明确不做的事 ​

UVHTTP 明确不做以下几件事。如果你的场景需要这些能力,UVHTTP 可能不适合你:

领域原因
HTTP/2HTTP/2 的多路复用与 libuv 的事件模型不兼容;需要应用层可见的多路复用,而不是连接的透明复用
HTTP/3基于 QUIC,需要完全不同的传输层,超出"轻量级 C 库"的范围
Windows 原生支持libuv 在 Windows 上工作,但 UVHTTP 的零拷贝路径(sendfile)和对 epoll 的深度依赖使 Windows 支持需要大量工作
异步 DNSlibuv 有内置的 DNS 解析,但 UVHTTP 作为服务器库不承担客户端角色
自动重连/健康检查这是业务层的事,不是协议层的事
配置热加载嵌入式场景通常不需要;需要热加载的可以在应用层实现
插件系统动态加载与嵌入式安全目标冲突;用户通过编译时链接扩展功能

上述边界不是永久性的。如果社区贡献使某项成为可能且不违背核心哲学,可以重新评估。


十、嵌入验证:真实场景是唯一有效的测试 ​

UVHTTP 的代码质量通过三层门禁保障(编译、测试、CI),但这三个层面都是在库内部验证的。qwrt 嵌入 uvhttp 时暴露了 6 类问题——这些问题在 uvhttp 自己的测试套件中全部通过,但在真实嵌入场景中集体失败。

教训:测试通过 ≠ 嵌入可用。

原则 ​

  1. 嵌入验证即测试——任何新功能、新 API、新模块,在发布前必须至少有一个真实嵌入者验证通过
  2. 无嵌入验证的代码视为未验证——即使内部测试全部通过
  3. 嵌入验证清单是基础设施——每次重要发布前,用嵌入清单验证所有已知的嵌入维度

qwrt 嵌入暴露的问题类别 ​

类别问题根因
枚举对齐llhttp 与 uvhttp 的方法枚举不对齐,强转损坏 POST/PUT/DELETE枚举值耦合未文档化,无 compile-time 断言
标准兼容gzip 输出 zlib 包装(0x78)而非标准 gzip(0x1f8b)测试验证了解压而非标准解码器验证
路径规范index 文件路径无前导斜杠 → 403路径验证与路径生成逻辑不匹配
协议合规HTTP 头匹配大小写敏感,拒绝合法大小写变体测试客户端使用规范大小写,掩盖了问题
处理管线路由未命中时无兜底 handler,嵌入者无法拦截设计时未考虑嵌入者需要最后一道防线
静态全局gzip 缓存使用模块级静态全局变量违反了"零全局变量"原则,CI 未检测

这些类别应作为每次嵌入验证的检查维度。

对哲学文档的补充 ​

这份文档的前八章定义了 UVHTTP 的设计哲学(做什么、不做什么、为什么)。本章定义了 UVHTTP 的验证哲学(如何证明做对了)。

UVHTTP 的验证栈:

┌─────────────────────────────────────┐
│ 第三层:嵌入验证(真实场景)        │ ← 本章定义
├─────────────────────────────────────┤
│ 第二层:构建组合验证(多配置)      │ ← 已补齐(build-matrix)
├─────────────────────────────────────┤
│ 第一层:测试 + ASan/UBSan(基础)   │ ← 已完善
└─────────────────────────────────────┘

第一层已完善;第二层已由 ci-pr.yml 的 build-matrix job 补齐(验证全开/最简/ROUTER_CACHE/静态文件/无压缩等组合);第三层通过嵌入验证清单落实。本章是第一层到第三层的桥梁。


十一、版本历史与变更 ​

版本日期变更
1.02026-08-20初始文档
1.12026-08-20补充"低资源使用优先"、修复第九节格式
1.22026-08-20新增顶层章节"零成本抽象"
1.32026-08-20更新验证栈:第二层构建组合验证已由 ci-pr.yml build-matrix 补齐

Released under MIT License