Golang REST API返回损坏Zip文件问题排查求助
问题描述
从MongoDB集合查询记录,将每条记录存为JSON文件后打包成Zip,通过Golang REST API返回。服务器端生成的Zip文件测试正常,但Postman接收响应保存为Zip后文件损坏,且响应文件大小与服务器端一致。尝试修改响应Header、更换Zip读取方式等均无效,需排查原因及解决办法。
相关代码
// each record should be saved in a file var records []iodefRepo.IodefRecord if cur, err := h.iodefRepo.IodefCollection().Find(helper.Context(), filter, options.Find().SetSort(M{"received_at": -1})); err != nil { return helper.WrapInternalErr("while finding iodef, err=" + err.Error()) } else if err := cur.All(helper.Context(), &records); err != nil { return helper.WrapInternalErr("while un-cursoring records, err=" + err.Error()) } // ------------------------------------------------------- resultFile, err := os.Create(fmt.Sprint(fileName, ".zip")) if err != nil { return helper.WrapInternalErr("while creating the result file, err=" + err.Error()) } writer := zip.NewWriter(resultFile) // files is a [][]byte that each element is []byte of json.Unmarshal for i, f := range files { if file, err := writer.Create(fmt.Sprint("IncidentName=", records[i].Document.Incidents[0].IncidentID.Name, ", IncidentData=", records[i].Document.Incidents[0].IncidentID.Data, ".", format)); err != nil { return helper.WrapInternalErr("while creating iodef file, err=" + err.Error()) } else if _, err := file.Write(f); err != nil { return helper.WrapInternalErr("while writing to iodef file, err=" + err.Error()) } } helper.AddResponseHeader("Content-Type", "application/zip") helper.AddResponseHeader("Content-Transfer-Encoding", "binary") helper.AddResponseHeader("Content-Disposition", "attachment; filename=export.zip") _ = writer.Close() _ = resultFile.Close() if result, err := os.ReadFile(fileName + ".zip"); err != nil { return helper.WrapInternalErr("while reading zip file, err=" + err.Error()) } else { // this result which is a []byte will be write to standard ResponseWriter // the same as err := w.Write(result); mention that I have ckecked and there // is no error in any of the steps and everything is done without any errors. return helper.WrapOk(result) }
问题原因及解决办法
核心问题
- Zip写入未完成即读取:调用
writer.Close()后,底层文件缓冲区可能未完全刷新到磁盘,此时直接读取会导致Zip结构不完整。 - 错误被忽略:
writer.Close()和resultFile.Close()的错误被直接丢弃,这两个步骤失败会导致Zip损坏,但代码未捕获。 - 磁盘IO冗余:没必要先写入磁盘再读取,内存中生成Zip更高效且避免磁盘同步问题。
修复方案
方案一:修复磁盘文件读写流程
若必须使用磁盘文件,需确保写入完全完成后再读取:
// ... 前置查询代码 ... resultFile, err := os.Create(fmt.Sprint(fileName, ".zip")) if err != nil { return helper.WrapInternalErr("while creating the result file, err=" + err.Error()) } defer resultFile.Close() writer := zip.NewWriter(resultFile) // 写入Zip内容 for i, f := range files { file, err := writer.Create(fmt.Sprint("IncidentName=", records[i].Document.Incidents[0].IncidentID.Name, ", IncidentData=", records[i].Document.Incidents[0].IncidentID.Data, ".", format)) if err != nil { return helper.WrapInternalErr("while creating iodef file, err=" + err.Error()) } if _, err := file.Write(f); err != nil { return helper.WrapInternalErr("while writing to iodef file, err=" + err.Error()) } } // 必须检查writer关闭错误,这一步会写入Zip目录结构 if err := writer.Close(); err != nil { return helper.WrapInternalErr("while closing zip writer, err=" + err.Error()) } // 强制刷新缓冲区到磁盘 if err := resultFile.Sync(); err != nil { return helper.WrapInternalErr("while syncing result file, err=" + err.Error()) } // 关闭文件后重新读取 if err := resultFile.Close(); err != nil { return helper.WrapInternalErr("while closing result file, err=" + err.Error()) } result, err := os.ReadFile(fileName + ".zip") if err != nil { return helper.WrapInternalErr("while reading zip file, err=" + err.Error()) } // 设置响应头,移除多余的Content-Transfer-Encoding helper.AddResponseHeader("Content-Type", "application/zip") helper.AddResponseHeader("Content-Disposition", "attachment; filename=export.zip") return helper.WrapOk(result)
方案二:内存中生成Zip(推荐)
直接在内存中生成Zip,避免磁盘IO问题,更高效可靠:
// ... 前置查询代码 ... var buf bytes.Buffer writer := zip.NewWriter(&buf) // 写入Zip内容 for i, f := range files { file, err := writer.Create(fmt.Sprint("IncidentName=", records[i].Document.Incidents[0].IncidentID.Name, ", IncidentData=", records[i].Document.Incidents[0].IncidentID.Data, ".", format)) if err != nil { return helper.WrapInternalErr("while creating iodef file, err=" + err.Error()) } if _, err := file.Write(f); err != nil { return helper.WrapInternalErr("while writing to iodef file, err=" + err.Error()) } } // 关闭writer完成Zip写入 if err := writer.Close(); err != nil { return helper.WrapInternalErr("while closing zip writer, err=" + err.Error()) } // 设置响应头 helper.AddResponseHeader("Content-Type", "application/zip") helper.AddResponseHeader("Content-Disposition", "attachment; filename=export.zip") // 直接返回内存中的字节数组 return helper.WrapOk(buf.Bytes())
额外注意事项
- 不要忽略任何IO操作错误,尤其是
writer.Close(),该步骤负责写入Zip的目录结构,失败会直接导致Zip损坏。 Content-Transfer-Encoding: binary在HTTP/1.1中属于冗余设置,默认传输即为二进制,无需添加。- 使用
defer确保文件或writer被正确关闭,避免资源泄漏和写入不完整问题。
内容的提问来源于stack exchange,提问作者Amirhossein Dolatkhah
相关产品推荐
相关产品推荐

