forked from TrueCloudLab/frostfs-s3-gw
Use Content-Range in PATCH
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
b2682e49ea
commit
6daa8b4698
2 changed files with 113 additions and 26 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue