本文介绍使用 ETag 验证简单上传对象完整性的过程。
使用TOS进行数据上传或下载的过程中,可能会因为公网传输过程中在TOS外部的网络劫持、数据缓存等原因,导致数据不一致等问题。TOS提供了多种数据一致性相关的特性,您可以利用这些特性确保数据上传或下载的一致性。在TOS场景中,数据一致性校验分为上传对象一致性校验以及下载对象一致性校验,。
本实验介绍使用Etag验证简单上传场景下的数据完整性。
一、完整性效验原理
将对象上传到TOS时,它们可以作为单个对象上传,也可以通过分片进行上传。
当对象使用简单上传时,该对象的 Etag 即为整个对象的MD5摘要,根据上传完成后响应的Etag跟本地文件的Etag进行对比即可;对于已经上传完成,后续要对其进行完整性效验的场景,可以使用HeadObject来获取对象Etag,然后进行对比。
当对象使用分片上传时,该对象的 ETag 不是整个对象的 MD5 摘要。TOS 会在上传时计算每个分片的 MD5 摘要。MD5 摘要用于确定最终对象的 ETag。TOS 将 MD5 摘要的字节串联在一起,然后计算这些串联值的 MD5 摘要。创建 ETag 的最后一步为在末尾添加一个短横线和分片总数。
示例,考虑使用 ETag 为 C9A5A6878D97B48CC965C1E41859F034-14 的分片上传的对象上传。在本例中,C9A5A6878D97B48CC965C1E41859F034 是串联在一起的所有摘要的 MD5 摘要。-14 表示有 14 个分片与此对象的分片上传相关联。
您可以使用 GetObject 或 HeadObject 操作并指定与单个分片对齐的分片编号或字节范围,来获取单个分片的校验和。借助此方法,您可以使用该校验和验证单个分片,而无需等待所有分片都上传完毕后再验证数据完整性。使用此方法时,还可以仅请求未通过完整性测试的单个分片。
由于 TOS计算分片对象的校验和的方式,复制对象时,对象的校验和值可能会发生变化。如果您使用的是SDK 或 REST API,并且调用 CopyObject,TOS可以复制大小不超过 CopyObject API 操作限制的任何对象。无论对象是在单个请求中上传还是作为分片上传的一部分上传,TOS都可以作为单个操作执行此复制。使用 copy 命令,对象的校验和是完整对象的直接校验和。如果对象最初是使用分片上传进行上传的,那么即使数据没有变化,校验和值也会发生变化。
二、代码实现
package main import ( "context" "crypto/md5" "encoding/hex" "fmt" "github.com/volcengine/ve-tos-golang-sdk/v2/tos" "io/ioutil" "os" "strings" ) func log(msg string) { fmt.Println(msg + "\n") } // 获取文件的 md5 func Md5Value(bytes []byte) (md5s string) { h := md5.New() h.Write(bytes) md5s = hex.EncodeToString(h.Sum(nil)) return } // 获取文件的 md5 值 func GetFileMd5Hash(fileBytes []byte) string { hash := Md5Value(fileBytes) log(fmt.Sprintf("get file hash: %v", hash)) return hash } func main() { // 先计算文件的MD5 fileName := "test" path := fmt.Sprintf("/Users/xxxx/%s", fileName) localfile, err := os.Open(path) if err != nil { panic(err) } defer localfile.Close() all, err := ioutil.ReadAll(localfile) localMD5Values := GetFileMd5Hash(all) // 上传对象并获取返回的Etag var ( accessKey = "xxxxx" secretKey = "xxxxx" endpoint = "tos-cn-beijing.volces.com" region = "cn-beijing" ) client, errClient := tos.NewClientV2(endpoint, tos.WithRegion(region), tos.WithCredentials(tos.NewStaticCredentials(accessKey, secretKey))) if errClient != nil { panic(err) } file, _ := os.Open("/Users/xxxx/test") inputstruct := tos.PutObjectBasicInput{ Bucket: "xxxx", Key: "xxxx", } get, err := client.PutObjectV2(context.Background(), &tos.PutObjectV2Input{ inputstruct, file, }) if err != nil { fmt.Println(err) } EtagValue := strings.Trim(get.ETag, "\"") fmt.Println(EtagValue) if EtagValue == localMD5Values { fmt.Println("效验成功") } else { fmt.Println("效验失败") // 效验失败可以添加失败处理逻辑 } }
参考文档:
[1] TOS Go SDK