Skip to content

fix(h2): 发送前把 header field name 规范化为小写(附 webapp HTTP/3 问题报告) - #63

Merged
Antruly merged 2 commits into
masterfrom
fix/h2-lowercase-headers
Oct 27, 2026
Merged

Antruly merged 2 commits into
masterfrom
fix/h2-lowercase-headers

Conversation

@Antruly

@Antruly Antruly commented Oct 27, 2026

Copy link
Copy Markdown
Owner

h2 修复(本 PR 已就绪)

HTTP/2 的 field name 在线上必须全小写(RFC 9113 §8.2.1)。之前
build_response_nv() / build_trailer_nv() / 请求头构建器直接拿原始名字过
nghttp2_check_header_name():应用设置 X-Mixed-Case、Cache-Control 这类
混合大小写 Header 时返回 false,submit_* 返回 UV_EINVAL,流停在“提交过、
一个字节都没发”的状态,客户端只能等到超时。

最小复现(uvcpp_web_app + TLS + h2):

app.get("/api/health", [](uvcpp_web_request&, uvcpp_web_response& resp, uvcpp_web_next) {
  resp.set_header("Cache-Control", "no-store");  // 大写 -> 挂死
  resp.json_str("{\"ok\":true}");
  resp.end();
});

改成 cache-control 立即正常。

修复:

  • 响应头、尾随头、请求头三条出口统一把 field name 规范化为小写后再校验、组帧;
  • 小写后的名字存放在调用方的 name_store 里(与 store/nv 同寿命),
    避免 builder 返回后 nghttp2_submit_* 拿到悬垂指针;
  • 入方向仍按 RFC 要求校验(不替对端“修正”大小写)。

回归测试:tests/functional/web_ssl_app_h2_func.cpp 新增 /mixed-header
路由(设置 X-Mixed-Case),场景 1 请求它并断言 HTTP/2 200 + mixed-ok。
修复前该用例超时,修复后通过。

本地验证:

cmake -S . -B build-check -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DUVCPP_BUILD_WEB=ON -DUVCPP_BUILD_WEBAPP=ON -DUVCPP_BUILD_NET=ON \
  -DUVCPP_ENABLE_OPENSSL=ON -DUVCPP_ENABLE_ZLIB=ON -DUVCPP_ENABLE_NGHTTP2=ON \
  -DUVCPP_ENABLE_BROTLI=OFF -DUVCPP_ENABLE_QUIC=OFF -DUVCPP_ENABLE_HTTP3=OFF \
  -DUVCPP_BUILD_TESTS=ON
ninja -C build-check test_web_ssl_app_h2_func
./build-check/tests/functional/test_web_ssl_app_h2_func
# [web_ssl_app_h2] all scenarios OK

也在 1.8.2 树上用最小 webapp 用例验证:混合大小写 Header 修复前挂起,修复后
HTTP/2 200。


webapp 的 HTTP/3 缺口(需要负责人跟进)

本 PR 没有修 h3;下面是完整复现、日志和根因,方便按同一分支继续。

复现

  • libuvcpp 1.8.8,QUIC/H3 编译开启(OpenSSL 3.5.9),webapp 路由 + TLS。
  • app.http_server()->set_quic_ssl_context(&quic_tls);
    app.http_server()->listen_quic(port, host);,然后 app.start()。
  • 用 h3 客户端 GET /api/health。

现象

  1. webapp start() 无条件安装 http_->set_stream_claim(...);
    dispatch_h3_request() 只要看到 claim hook 就拒绝整条 h3 请求:
    [uvcpp_http_server] h3 上不支持 set_stream_claim() 装的认领钩子 ...
  2. 在 uvscope 侧调用 http_server()->clear_stream_claim() 后,请求确实进了
    webapp 路由,但响应被丢弃:
请求来自未登记的连接,已补登记为 3
连接 3 已断开,响应被丢弃(status 200)

h3 客户端 8 秒超时。此时底层 h3 本身是通的(同一服务端用 h3 客户端拿到过
HTTP/3 200,只是响应为空/默认)。

根因

  1. uvcpp_web_app::start()(src/webapp/uvcpp_web_app.cpp 约 2049 行)无条件
    set_stream_claim,即使没有任何 post_stream() 流式路由。h3 派发器
    (src/web/uvcpp_http_server.cpp 约 3261 行 refuse_claim)因此拒绝所有 h3
    请求。
  2. uvcpp_web_app::on_http_request()(约 3441 行)把请求绑定到
    uvcpp_web_context / connection_id;响应最终走
    uvcpp_web_app::send_response(),里面 reg_client(ctx.connection_id()) 在 h3
    上返回 nullptr(h3 没有 TCP client),于是日志“连接已断开,响应被丢弃”。

建议修法

  • 只在存在 post_stream() 路由时安装 claim hook(或在 h3 上忽略它);
  • h3(client == nullptr)不要走 TCP 绑定的 context/send_response:
    • 同步响应:把填充好的 resp 交回 dispatch_h3_request() 的
      send_h3_response(),不要自己发;
    • 异步/deferred:使用 req.reply + send_reply() 的 h3 deferred 路径;
  • 同时把 h3 的 h3_header 组帧路径也做同样的 field name 小写规范化(nghttp3
    同样要求小写)。

环境

  • libuvcpp 1.8.8(上游最新 tag),QUIC/H3=ON,OpenSSL 3.5.9。
  • 对照:低层 uvcpp_http_server 的 h3 用例/http3_web_func 正常;问题在
    uvcpp_web_app 这一层。

本 PR 标记为 draft:h2 修复可直接合并;h3 webapp 部分请负责人按上面的
根因和建议继续。

Antruly and others added 2 commits October 27, 2026 21:26
HTTP/2 的 field name 在线上必须全小写(RFC 9113 §8.2.1)。之前的
build_response_nv() / build_trailer_nv() / 请求头构建器直接拿原始名字
过 nghttp2_check_header_name():应用设置 `X-Mixed-Case` 这类 Header 时
返回 false,submit_* 返回 UV_EINVAL 且流不会发送,客户端只能等到超时。
uvscope 的 TLS h2 直连就是这样挂死的。

- 在 h2 响应/尾随头/请求头三条出口统一小写化,再校验、组帧
- 小写后的名字放在调用方的 name_store 里,保证 nghttp2_submit_* 期间
  指针有效(不能放 builder 的局部变量)
- web_ssl_app_h2_func 增加 /mixed-header 回归用例

验证:本地 1.8.2 构建 + 最小 webapp 用例,修复前混合大小写 Header 挂起,
修复后 HTTP/2 200;h3 的 webapp 响应问题另见 PR 描述。
`uvcpp_h2_session.cpp` 的组帧边界新增约 30 行(`lower_header_name` /
`append_lower_header`),两条腿之后的引用整体后移。4 条都是同宽平移
(+19 / +23 / +27),没有歧义;锁文件按 `--update` 重刷。

- doc/http2-guide.md: 1216→1235、1322→1345
- doc/http2-status.md: 1210-1216→1229-1235、1691→1718
@Antruly
Antruly marked this pull request as ready for review October 27, 2026 13:36
@Antruly
Antruly merged commit 95a9b95 into master Oct 27, 2026
47 of 49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant