Nginx 迷思之请求缓冲与 Go 的分块传输编码

最近在自己的某个项目中频繁被用户反馈的一个文件上传bug, 测试也总是无法复现, debug 许久才找到问题根源, 以此记录一下.

项目中实现了兼容多种服务的文件上传接口的功能, 其中之一就是广为人知的 Alist .

这部分代码如下:

go
type Alist struct {
	client    *http.Client
	token     string
	baseURL   string
    // ...
}

func (a *Alist) Save(ctx context.Context, reader io.Reader, path string) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodPut, a.baseURL+"/api/fs/put", reader)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Authorization", a.token)
	req.Header.Set("File-Path", path)
	req.Header.Set("Content-Type", "application/octet-stream")

	resp, err := a.client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

    // ...
}

自从发布以来, 就收到一些用户反馈无法向 Alist 上传的问题, 但是无论是开发时的测试还是自己的"生产"环境都无法复现.

现在细说来这个问题出现的原因很简单, 根据 Alist 文档issue #5319 可知 Alist 的文件上传接口不支持 chunked 模式, 必须传递 Content-Length 指定文件大小.

上面的代码里没有设置 Content-Length , 且请求体是以 io.Reader 传递给 http.Client , 查看 net/http 源码 就可以发现, 此时就会使用 chunked 模式传输 (其实这本来就是我想要的, 但谁知道 alist 不支持).

而 Alist 应该返回 400 错误: strconv.ParseInt: parsing "": invalid syntax , 也就是解析不到 Content-Length

但是自己测试时却仍然可以上传成功, 是由于我的环境中使用 nginx 反向代理了 Alist , 根据 nginx 文档 可知, nginx 默认开启了请求缓冲 ( proxy_request_buffering ) .

向 nginx 上传文件时, nginx 会将请求体完整地缓冲到内存或硬盘中, 然后将设置了正确 Content-Length 头的新请求转发给后端.


Q.E.D.
Linux 安装最新 FFmpeg 和 FFprobe