Golang实现HTTP流式下载时如何获取Gzip文件的未压缩长度
问题解决方案
核心原理
标准gzip格式(RFC 1952)规定,完整的gzip文件最后4个字节固定存储未压缩数据的原始长度,以小端序32位整数形式保存(值为未压缩长度对2^32取模)。针对本地磁盘存储的完整.gz文件,完全不需要读取、解压整个文件内容,直接定位到文件尾部读取这4个字节即可拿到原始长度。
关键注意事项
- 该方案仅适用于本地完整存储的gzip文件,不适用于网络传输中的不完整gzip流
- gzip协议本身存在32位长度限制:当未压缩数据超过4GB时,尾部存储的长度值会出现溢出,此时不建议硬编码
Content-Length,可直接让服务端使用chunked流式编码传输 - Go标准库
compress/gzip不提供提前读取该长度的方法,是因为需要兼容流式gzip场景,这类场景下根本无法提前获取到文件尾部的内容
实现代码
注意:使用上述工具函数时,需要额外导入标准库
encoding/binary包,用于处理小端序整数转换。
首先是独立获取gzip未压缩长度的工具函数:
func getGzipUncompressedSize(filePath string) (int64, error) { f, err := os.Open(filePath) if err != nil { return 0, err } defer f.Close() // 获取文件元信息,拿到文件总大小 fileInfo, err := f.Stat() if err != nil { return 0, err } totalSize := fileInfo.Size() // 最小gzip文件长度校验,防止非法文件触发定位越界 if totalSize < 18 { return 0, fmt.Errorf("invalid gzip file format") } // 直接定位到文件末尾前4字节,也就是ISIZE字段的位置,不需要读取中间内容 _, err = f.Seek(totalSize-4, io.SeekStart) if err != nil { return 0, err } // 读取4字节小端序整数,就是未压缩的原始长度 var sizeBuf [4]byte _, err = io.ReadFull(f, sizeBuf[:]) if err != nil { return 0, err } return int64(binary.LittleEndian.Uint32(sizeBuf[:])), nil }
然后修改原有的HTTP下载处理逻辑:
func DownloadHandler(w http.ResponseWriter, r *http.Request) { path := "/path/to/thefile.gz" // 调用工具函数拿未压缩长度,全程只读取4字节文件内容,无大文件内存开销 contentLen, err := getGzipUncompressedSize(path) if err != nil { w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "404") return } openfile, err := os.Open(path) if err != nil { w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "404") return } defer openfile.Close() fz, err := gzip.NewReader(openfile) if err != nil { w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "404") return } defer fz.Close() // 正常设置响应头,直接使用拿到的未压缩长度 w.Header().Set("Content-Disposition", "attachment; filename=test") w.Header().Set("Content-Length", strconv.FormatInt(contentLen, 10)) w.Header().Set("Content-Type", "text/csv") // 流式传输解压内容,全程无全量文件内存占用 io.Copy(w, fz) }
补充说明
- 不要依赖
gzip.Reader读取完成后的内部长度字段,此时已经完整读取、解压了全部文件内容,和原有ioutil.ReadAll的低效方案没有区别 - 当未压缩文件超过4GB时,gzip协议本身的ISIZE字段已经无法存储正确长度,此时不需要强行设置
Content-Length头,Go的HTTP服务会自动采用chunked编码传输,绝大多数HTTP客户端都能正常兼容
内容的提问来源于stack exchange,提问作者Bravo Delta
相关产品推荐
相关产品推荐

