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

Go net/http 针对Expect:100-continue未返回100 Continue的问题

问题定性

这不是Go net/http的实现bug,也不是对HTTP规范的理解偏差——本质是框架针对无请求体边界标识请求的默认安全兜底逻辑,与部分不按规范发请求头的客户端产生了行为冲突。

触发无100响应的边界条件

根据测试结果可以复现:当请求同时满足以下三个条件时,Go net/http 服务端不会自动返回HTTP/1.1 100 Continue:

  • 携带 Expect: 100-continue 请求头
  • 未携带非零值的 Content-Length 请求头
  • 未携带 Transfer-Encoding: chunked 请求头

当请求携带非零Content-Length、或者Chunked分块编码头时,框架会严格按规范自动返回100 Continue响应。

框架默认逻辑的设计原因

按照RFC 7231的设计思路,100 Continue机制的核心是让服务端可以在不浪费带宽的情况下,提前拒绝不合法的请求。但这个机制的生效有隐含前提:服务端必须能明确判断客户端接下来要传多长的请求体,否则就会面临连接被恶意挂起的风险——比如客户端发完请求头就不发数据,占着TCP连接不释放。

如果请求没有任何标识请求体边界的头(既不知道内容长度,也没有分块编码标记),框架的默认处理逻辑是直接终止请求流程,不会等待客户端传输请求体,自然也就不会提前发送100 Continue响应。这种行为本身是符合HTTP规范的:规范明确允许服务端在收到无明确请求体边界的100-continue请求时,直接返回最终状态码,不需要先返回100。

适配非标准客户端的解决方案

针对推流到Icecast的ffmpeg这类不会规范携带请求体边界头的客户端,不需要依赖框架的自动100响应逻辑,可以在请求处理函数的最开头主动检测头、手动发送100响应:

func main() {
    http.HandleFunc("/", func(writer http.ResponseWriter, r *http.Request) {
        // 业务逻辑最开头处理100 Continue响应
        if r.Header.Get("Expect") == "100-continue" {
            rc := http.NewResponseController(writer)
            if err := rc.WriteContinue(); err != nil {
                return
            }
        }

        log.Printf("Expect header: %v\n", r.Header.Get("Expect"))
        log.Printf("Transfer-Encoding header: %v\n", r.Header.Get("Transfer-Encoding"))

        buf := new(bytes.Buffer)
        defer func() {
            log.Printf("Buffer size: %d\n", buf.Len())
        }()
        defer r.Body.Close()

        log.Println("Writing.")
        io.Copy(buf, r.Body)
    })

    log.Fatal(http.ListenAndServe(":3948", nil))
}

注意事项:

  • 必须把手动发100的逻辑放在读r.Body之前调用,否则会违反HTTP规范,导致客户端等待超时
  • 框架会自动检测响应流是否已经写入过100状态码,不会出现重复发送100响应的冲突
  • 这种适配方式可以覆盖所有带Expect: 100-continue的请求,不管客户端有没有携带请求体边界标识头,都能正常收到100响应

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.30 11:15:05