环境
- OpenList 版本:
v4.2.3-aio(Docker 镜像 openlistteam/openlist:v4.2.3-aio)
- S3 endpoint:
http://127.0.0.1:5246
- Bucket:
tianyi(后端为天翼云盘)
复现步骤
# PUT
aws --endpoint-url http://127.0.0.1:5246 s3api put-object \
--bucket tianyi --key test.txt --body /tmp/test.txt
# HEAD
aws --endpoint-url http://127.0.0.1:5246 s3api head-object \
--bucket tianyi --key test.txt
实际行为
PUT ETag: "6ad255986677cad6b8fdc86e8f0800f4" ← 有值
HEAD ETag: "" ← 空字符串
HEAD 响应头:
Etag: ""
Content-Length: 10
Last-Modified: Thu, 09 Jul 2026 10:43:28 GMT
期望行为
HEAD 和 GET 应该返回与 PUT 一致的 ETag。
根因分析
server/s3/backend.go 中 HeadObject() 和 GetObject() 返回的 gofakes3.Object 的 Hash 字段未被填充:
HeadObject (~L112):
// hash := getFileHashByte(fobj) // ← 计算 hash 的代码被注释
HeadObject 返回值 (~L125):
return &gofakes3.Object{
Name: objectName,
// Hash: hash, // ← ETag 从未被填充
Metadata: meta,
Size: size,
Contents: noOpReadCloser{},
}, nil
GetObject 返回值 (~L200): 同样 // Hash: "",。
PUT 能返回 ETag 是因为 gofakes3 库在上传过程中自行计算并写入响应;但 HEAD/GET 完全依赖后端返回的 Object.Hash 字段——该字段当前始终为零值。
影响
- 任何依赖
If-Match / If-None-Match 条件请求的 S3 客户端都会受影响
- rclone S3 remote 的文件变更检测(modtime + size + hash 比对)依赖 ETag
参考
环境
v4.2.3-aio(Docker 镜像openlistteam/openlist:v4.2.3-aio)http://127.0.0.1:5246tianyi(后端为天翼云盘)复现步骤
实际行为
HEAD 响应头:
期望行为
HEAD 和 GET 应该返回与 PUT 一致的 ETag。
根因分析
server/s3/backend.go中HeadObject()和GetObject()返回的gofakes3.Object的Hash字段未被填充:HeadObject (~L112):
// hash := getFileHashByte(fobj) // ← 计算 hash 的代码被注释HeadObject 返回值 (~L125):
GetObject 返回值 (~L200): 同样
// Hash: "",。PUT 能返回 ETag 是因为
gofakes3库在上传过程中自行计算并写入响应;但 HEAD/GET 完全依赖后端返回的Object.Hash字段——该字段当前始终为零值。影响
If-Match/If-None-Match条件请求的 S3 客户端都会受影响参考