feat: adds x-amz-object-size in PutObject response headers

Closes #1518

Adds the `x-amz-object-size` header to the `PutObject` response, indicating the size of the uploaded object. This change is applied to the POSIX, Azure, and S3 proxy backends.
This commit is contained in:
niksis02
2025-09-05 21:40:46 +04:00
parent 743707b9ae
commit 818e91ebde
8 changed files with 47 additions and 2 deletions

View File

@@ -367,6 +367,7 @@ func (az *Azure) PutObject(ctx context.Context, po s3response.PutObjectInput) (s
return s3response.PutObjectOutput{
ETag: convertAzureEtag(uploadResp.ETag),
Size: po.ContentLength,
}, nil
}

View File

@@ -2796,6 +2796,7 @@ func (p *Posix) PutObject(ctx context.Context, po s3response.PutObjectInput) (s3
// for directory object no version is created
return s3response.PutObjectOutput{
ETag: emptyMD5,
Size: &contentLength,
}, nil
}
@@ -2848,6 +2849,8 @@ func (p *Posix) PutObject(ctx context.Context, po s3response.PutObjectInput) (s3
}
defer f.cleanup()
objsize := f.size
hash := md5.New()
rdr := io.TeeReader(po.Body, hash)
@@ -2984,7 +2987,6 @@ func (p *Posix) PutObject(ctx context.Context, po s3response.PutObjectInput) (s3
return s3response.PutObjectOutput{}, fmt.Errorf("set versionId attr: %w", err)
}
}
err = f.link()
if errors.Is(err, syscall.EEXIST) {
return s3response.PutObjectOutput{
@@ -3042,6 +3044,7 @@ func (p *Posix) PutObject(ctx context.Context, po s3response.PutObjectInput) (s3
ChecksumSHA1: checksum.SHA1,
ChecksumSHA256: checksum.SHA256,
ChecksumCRC64NVME: checksum.CRC64NVME,
Size: &objsize,
ChecksumType: checksum.Type,
}, nil
}

View File

@@ -938,6 +938,7 @@ func (s *S3Proxy) PutObject(ctx context.Context, input s3response.PutObjectInput
ChecksumCRC64NVME: output.ChecksumCRC64NVME,
ChecksumSHA1: output.ChecksumSHA1,
ChecksumSHA256: output.ChecksumSHA256,
Size: output.Size,
}, nil
}

View File

@@ -725,6 +725,7 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) {
"x-amz-checksum-sha256": res.ChecksumSHA256,
"x-amz-checksum-type": utils.ConvertToStringPtr(res.ChecksumType),
"x-amz-version-id": &res.VersionID,
"x-amz-object-size": utils.ConvertPtrToStringPtr(res.Size),
},
MetaOpts: &MetaOptions{
ContentLength: contentLength,

View File

@@ -1036,6 +1036,7 @@ func TestS3ApiController_CopyObject(t *testing.T) {
func TestS3ApiController_PutObject(t *testing.T) {
str := ""
emptyStringPtr := &str
objSize := int64(120)
tests := []struct {
name string
@@ -1148,6 +1149,7 @@ func TestS3ApiController_PutObject(t *testing.T) {
"x-amz-checksum-sha256": nil,
"x-amz-checksum-type": nil,
"x-amz-version-id": emptyStringPtr,
"x-amz-object-size": nil,
},
MetaOpts: &MetaOptions{
BucketOwner: "root",
@@ -1188,6 +1190,7 @@ func TestS3ApiController_PutObject(t *testing.T) {
ChecksumCRC64NVME: utils.GetStringPtr("crc64nvme"),
ChecksumType: types.ChecksumTypeComposite,
VersionID: "versionId",
Size: &objSize,
},
},
output: testOutput{
@@ -1201,6 +1204,7 @@ func TestS3ApiController_PutObject(t *testing.T) {
"x-amz-checksum-sha256": utils.GetStringPtr("sha256"),
"x-amz-checksum-type": utils.GetStringPtr(string(types.ChecksumTypeComposite)),
"x-amz-version-id": utils.GetStringPtr("versionId"),
"x-amz-object-size": utils.ConvertToStringPtr(objSize),
},
MetaOpts: &MetaOptions{
BucketOwner: "root",

View File

@@ -37,6 +37,7 @@ type PutObjectOutput struct {
ChecksumSHA1 *string
ChecksumSHA256 *string
ChecksumCRC64NVME *string
Size *int64
ChecksumType types.ChecksumType
}

View File

@@ -3418,10 +3418,33 @@ func PutObject_racey_success(s *S3Conf) error {
func PutObject_success(s *S3Conf) error {
testName := "PutObject_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
_, err := putObjects(s3client, []string{"my-obj"}, bucket)
lgth := int64(100)
res, err := putObjectWithData(lgth, &s3.PutObjectInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
}, s3client)
if err != nil {
return err
}
// skip the ETag check for azure tests
if !s.azureTests {
etag, err := calculateEtag(res.data)
if err != nil {
return err
}
if getString(res.res.ETag) != etag {
return fmt.Errorf("expected ETag to be %s, intead got %s", getString(res.res.ETag), etag)
}
}
if res.res.Size == nil {
return fmt.Errorf("unexpected nil object Size")
}
if *res.res.Size != lgth {
return fmt.Errorf("expected the object size to be %v, instead got %v", lgth, *res.res.Size)
}
return nil
})
}

View File

@@ -17,6 +17,7 @@ package integration
import (
"bytes"
"context"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
@@ -1798,3 +1799,13 @@ func testOPTIONSEdnpoint(s *S3Conf, bucket, origin, method string, headers strin
return comparePreflightResult(expected, result)
}
func calculateEtag(data []byte) (string, error) {
h := md5.New()
_, err := h.Write(data)
if err != nil {
return "", err
}
dataSum := h.Sum(nil)
return fmt.Sprintf("\"%s\"", hex.EncodeToString(dataSum[:])), nil
}