Use Content-Range in PATCH

Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
Marina Biryukova 2024-06-11 17:48:21 +03:00
parent b2682e49ea
commit 6daa8b4698
2 changed files with 113 additions and 26 deletions

View file

@ -1,14 +1,17 @@
package handler
import (
"fmt"
"net/http"
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"go.uber.org/zap"
)
func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
@ -17,10 +20,14 @@ func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
reqInfo = middleware.GetReqInfo(ctx)
)
startByteStr := reqInfo.URL.Query().Get("startByte")
startByte, err := strconv.ParseInt(startByteStr, 10, 64)
if err != nil || startByte < 0 {
h.logAndSendError(w, "invalid start byte", reqInfo, err)
byteRange, err := parseByteRange(r.Header.Get(api.ContentRange))
if err != nil {
h.logAndSendError(w, "could not parse byte range", reqInfo, errors.GetAPIError(errors.ErrInvalidRange), zap.Error(err))
return
}
if uint64(r.ContentLength) != (byteRange.End - byteRange.Start + 1) {
h.logAndSendError(w, "content-length must be equal to byte range length", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
return
}
@ -60,7 +67,7 @@ func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
return
}
if uint64(startByte) > srcSize {
if byteRange.Start > srcSize {
h.logAndSendError(w, "start byte is greater than object size", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
return
}
@ -77,13 +84,13 @@ func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
}
params := &layer.PatchObjectParams{
Object: srcObjInfo,
BktInfo: bktInfo,
SrcSize: srcSize,
DstSize: srcSize + size,
Header: metadata,
NewBytes: r.Body,
StartByte: uint64(startByte),
Object: srcObjInfo,
BktInfo: bktInfo,
SrcSize: srcSize,
Header: metadata,
NewBytes: r.Body,
NewBytesSize: size,
Range: byteRange,
}
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, bktInfo.LocationConstraint)
@ -106,3 +113,44 @@ func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
func parseByteRange(rangeStr string) (*layer.RangeParams, error) {
const (
prefix = "bytes "
suffix = "/*"
)
if rangeStr == "" {
return nil, fmt.Errorf("empty range")
}
if !strings.HasPrefix(rangeStr, prefix) {
return nil, fmt.Errorf("unknown unit in range header")
}
if !strings.HasSuffix(rangeStr, suffix) {
return nil, fmt.Errorf("invalid size in range header")
}
parts := strings.Split(strings.TrimSuffix(strings.TrimPrefix(rangeStr, prefix), suffix), "-")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid range: %s", rangeStr)
}
start, err := strconv.ParseUint(parts[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid start byte: %s", parts[0])
}
end, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid end byte: %s", parts[1])
}
if start > end {
return nil, fmt.Errorf("start byte is greater than end byte")
}
return &layer.RangeParams{
Start: start,
End: end,
}, nil
}

View file

@ -169,10 +169,10 @@ type (
Object *data.ObjectInfo
BktInfo *data.BucketInfo
SrcSize uint64
DstSize uint64
Header map[string]string
NewBytes io.Reader
StartByte uint64
NewBytesSize uint64
Range *RangeParams
Encryption encryption.Params
CopiesNumbers []uint32
}
@ -655,9 +655,7 @@ func (n *layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*data.Exte
}
func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.ExtendedObjectInfo, error) {
if p.StartByte == 0 || p.StartByte == p.SrcSize {
var r io.Reader
if p.Range.Start == p.SrcSize {
objPayload, err := n.GetObject(ctx, &GetObjectParams{
ObjectInfo: p.Object,
Versioned: true,
@ -668,17 +666,46 @@ func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
return nil, fmt.Errorf("get object to patch: %w", err)
}
if p.StartByte == 0 {
r = io.MultiReader(p.NewBytes, objPayload)
} else {
r = io.MultiReader(objPayload, p.NewBytes)
return n.PutObject(ctx, &PutObjectParams{
BktInfo: p.BktInfo,
Object: p.Object.Name,
Size: p.SrcSize + p.NewBytesSize,
Reader: io.MultiReader(objPayload, p.NewBytes),
Header: p.Header,
Encryption: p.Encryption,
CopiesNumbers: p.CopiesNumbers,
})
}
if p.Range.Start == 0 {
if p.Range.End >= p.SrcSize-1 {
return n.PutObject(ctx, &PutObjectParams{
BktInfo: p.BktInfo,
Object: p.Object.Name,
Size: p.NewBytesSize,
Reader: p.NewBytes,
Header: p.Header,
Encryption: p.Encryption,
CopiesNumbers: p.CopiesNumbers,
})
}
objPayload, err := n.GetObject(ctx, &GetObjectParams{
ObjectInfo: p.Object,
Range: &RangeParams{Start: p.Range.End + 1, End: p.SrcSize - 1},
Versioned: true,
BucketInfo: p.BktInfo,
Encryption: p.Encryption,
})
if err != nil {
return nil, fmt.Errorf("get object range to patch: %w", err)
}
return n.PutObject(ctx, &PutObjectParams{
BktInfo: p.BktInfo,
Object: p.Object.Name,
Size: p.DstSize,
Reader: r,
Size: p.SrcSize - 1 - p.Range.End + p.NewBytesSize,
Reader: io.MultiReader(p.NewBytes, objPayload),
Header: p.Header,
Encryption: p.Encryption,
CopiesNumbers: p.CopiesNumbers,
@ -687,7 +714,7 @@ func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
objPayload1, err := n.GetObject(ctx, &GetObjectParams{
ObjectInfo: p.Object,
Range: &RangeParams{Start: 0, End: p.StartByte - 1},
Range: &RangeParams{Start: 0, End: p.Range.Start - 1},
Versioned: true,
BucketInfo: p.BktInfo,
Encryption: p.Encryption,
@ -696,9 +723,21 @@ func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
return nil, fmt.Errorf("get object range 1 to patch: %w", err)
}
if p.Range.End >= p.SrcSize-1 {
return n.PutObject(ctx, &PutObjectParams{
BktInfo: p.BktInfo,
Object: p.Object.Name,
Size: p.Range.Start + p.NewBytesSize,
Reader: io.MultiReader(objPayload1, p.NewBytes),
Header: p.Header,
Encryption: p.Encryption,
CopiesNumbers: p.CopiesNumbers,
})
}
objPayload2, err := n.GetObject(ctx, &GetObjectParams{
ObjectInfo: p.Object,
Range: &RangeParams{Start: p.StartByte, End: p.SrcSize - 1},
Range: &RangeParams{Start: p.Range.End + 1, End: p.SrcSize - 1},
Versioned: true,
BucketInfo: p.BktInfo,
Encryption: p.Encryption,
@ -710,7 +749,7 @@ func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
return n.PutObject(ctx, &PutObjectParams{
BktInfo: p.BktInfo,
Object: p.Object.Name,
Size: p.DstSize,
Size: p.SrcSize,
Reader: io.MultiReader(objPayload1, p.NewBytes, objPayload2),
Header: p.Header,
Encryption: p.Encryption,