分块下载合并二进制文件哈希不符致损坏,求助排查问题
未校验HTTP响应状态码:你直接调用
http.Get后就复制内容,但如果服务器返回4xx/5xx错误(比如分块不存在、权限问题),resp.Body里会是错误页面的HTML内容而非分块文件。二进制文件混入HTML后哈希必然错误,文本文件可能刚好没触发这类错误。应该在获取响应后先检查resp.StatusCode == http.StatusOK,非200则直接报错。未验证分块哈希:上传时校验了分块哈希,但下载阶段没验证每个下载的分块哈希是否和
hash.Hash一致。如果分块在传输中损坏(丢包、篡改),写入文件后会导致整体哈希错误。建议下载每个分块后,先计算其哈希再写入目标文件,或者先保存到临时文件校验后再合并。分块顺序错误:确认
fileInfo.FileInfo.FileBlocks的顺序是否和上传时的拆分顺序完全一致。文本文件顺序错误可能还能勉强读取,但二进制文件(图片、视频等)顺序错乱会直接损坏,哈希也会不符。可以打印每个分块的索引和哈希,对比上传时的拆分顺序。HTTP自动解压篡改二进制内容:Go的
http.Client默认会自动解压Content-Encoding为gzip/deflate的响应内容。如果服务器错误地给二进制分块设置了Content-Encoding: gzip,客户端自动解压后会改变分块的原始二进制内容。可以通过设置http.Client的Transport.DisableCompression = true禁用自动解压,测试是否解决问题。io.Copy未完整写入分块:你打印了
n但没对比分块的预期大小。如果io.Copy返回的n不等于该分块的实际大小(比如网络中断导致提前结束),会导致目标文件缺失部分内容,哈希错误。可以通过resp.ContentLength获取预期大小,对比n是否一致,不一致则报错重试。
out, err := os.Create(fileInfo.FileInfo.FileName) if err != nil { panic(err) } defer out.Close() // 用defer确保文件关闭 // 禁用HTTP自动解压,避免二进制内容被篡改 client := &http.Client{ Transport: &http.Transport{ DisableCompression: true, }, } for _, hash := range fileInfo.FileInfo.FileBlocks { keeper := getKeeperByHash(hash.Hash, fileInfo.HasHash) log.Println("Downloading", hash.Hash) downloadURL := &url.URL{ Scheme: "http", Host: fmt.Sprintf("%s:%d", keeper.Ip, keeper.Port), Path: "file/get", } param := url.Values{} param.Add("hash", hash.Hash) downloadURL.RawQuery = param.Encode() resp, err := client.Get(downloadURL.String()) if err != nil { panic(err) } defer resp.Body.Close() // 确保每个响应体都被关闭 // 检查响应状态码 if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("download block %s failed: status code %d", hash.Hash, resp.StatusCode)) } // 检查分块大小(假设hash结构体包含Size字段) if resp.ContentLength != hash.Size { panic(fmt.Sprintf("block %s size mismatch: expected %d, got %d", hash.Hash, hash.Size, resp.ContentLength)) } n, err := io.Copy(out, resp.Body) log.Println("written bytes:", n) if err != nil { panic(err) } // 验证写入大小是否符合预期 if n != hash.Size { panic(fmt.Sprintf("block %s not fully written: expected %d, wrote %d", hash.Hash, hash.Size, n)) } } hash1, err := GetFileHashAlt(fileInfo.FileInfo.FileName) if err != nil { panic(err) // 不要忽略哈希计算的错误 } if hash1 != fileInfo.FileInfo.FileHash { log.Println("calculated hash:", hash1) log.Println("expected hash:", fileInfo.FileInfo.FileHash) panic("HASH NOT THE SAME") } log.Println("hash match:", hash1)
内容的提问来源于stack exchange,提问作者balamutik

