You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NgHttp2单个请求触发两次request data handler问题排查

问题根因

nghttp2 asio 服务端的 on_data 回调设计为两次触发机制:

  1. 收到请求体数据时触发,此时len参数为实际接收到的字节数(大于0),如果请求体分多帧传输还可能触发多次大于0的回调
  2. 整个请求体全部接收完成后,会额外触发一次len == 0的回调,用于通知上层业务请求数据已接收完毕

你当前的代码没有判断len参数的有效值,只要触发回调就执行计数+1、返回响应的逻辑:

  • 不带请求体的请求:只会触发1次len == 0的回调,所以输出1条日志符合预期
  • 带10字节请求体的请求:先触发1次len=10的回调(输出1),再触发1次len=0的回调(输出2),所以最终输出2条日志

修复方案

根据业务需求选择任意一种即可:

方案1:仅处理实际携带数据的回调

只在len > 0时执行业务逻辑,忽略收尾的空回调:

server.handle("/", [&count](const request &req, const response &res) {
    req.on_data([&res, &count](const uint8_t *data, std::size_t len) {
        // 忽略请求接收完成的空回调
        if (len == 0) {
            return;
        }
        std::cerr << ++count << '\n';
        res.write_head(200);
        res.end("done");
    });
});

方案2:等待全部请求体接收完成后再处理

如果需要等所有数据收完再统一处理,只在len == 0时执行业务逻辑,之前的回调只负责拼接数据:

server.handle("/", [&count](const request &req, const response &res) {
    // 存储完整请求体
    auto req_body = std::make_shared<std::string>();
    req.on_data([&res, &count, req_body](const uint8_t *data, std::size_t len) {
        if (len > 0) {
            req_body->append(reinterpret_cast<const char*>(data), len);
            return;
        }
        // 所有数据接收完成后再执行核心逻辑
        std::cerr << ++count << '\n';
        res.write_head(200);
        res.end("done");
    });
});

方案3:添加处理标记避免重复执行

如果业务逻辑允许提前返回响应,可以加个状态标记防止回调逻辑重复执行:

server.handle("/", [&count](const request &req, const response &res) {
    auto processed = std::make_shared<bool>(false);
    req.on_data([&res, &count, processed](const uint8_t *data, std::size_t len) {
        if (*processed) {
            return;
        }
        *processed = true;
        std::cerr << ++count << '\n';
        res.write_head(200);
        res.end("done");
    });
});

内容的提问来源于stack exchange,提问作者user16516564

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 10:27:03