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
相关产品推荐
相关产品推荐

