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
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"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/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
"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/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
|
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)
|
reqInfo = middleware.GetReqInfo(ctx)
|
||||||
)
|
)
|
||||||
|
|
||||||
startByteStr := reqInfo.URL.Query().Get("startByte")
|
byteRange, err := parseByteRange(r.Header.Get(api.ContentRange))
|
||||||
startByte, err := strconv.ParseInt(startByteStr, 10, 64)
|
if err != nil {
|
||||||
if err != nil || startByte < 0 {
|
h.logAndSendError(w, "could not parse byte range", reqInfo, errors.GetAPIError(errors.ErrInvalidRange), zap.Error(err))
|
||||||
h.logAndSendError(w, "invalid start byte", reqInfo, 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +67,7 @@ func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if uint64(startByte) > srcSize {
|
if byteRange.Start > srcSize {
|
||||||
h.logAndSendError(w, "start byte is greater than object size", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
h.logAndSendError(w, "start byte is greater than object size", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -77,13 +84,13 @@ func (h *handler) PatchHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
params := &layer.PatchObjectParams{
|
params := &layer.PatchObjectParams{
|
||||||
Object: srcObjInfo,
|
Object: srcObjInfo,
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
SrcSize: srcSize,
|
SrcSize: srcSize,
|
||||||
DstSize: srcSize + size,
|
Header: metadata,
|
||||||
Header: metadata,
|
NewBytes: r.Body,
|
||||||
NewBytes: r.Body,
|
NewBytesSize: size,
|
||||||
StartByte: uint64(startByte),
|
Range: byteRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, bktInfo.LocationConstraint)
|
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
|
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
|
Object *data.ObjectInfo
|
||||||
BktInfo *data.BucketInfo
|
BktInfo *data.BucketInfo
|
||||||
SrcSize uint64
|
SrcSize uint64
|
||||||
DstSize uint64
|
|
||||||
Header map[string]string
|
Header map[string]string
|
||||||
NewBytes io.Reader
|
NewBytes io.Reader
|
||||||
StartByte uint64
|
NewBytesSize uint64
|
||||||
|
Range *RangeParams
|
||||||
Encryption encryption.Params
|
Encryption encryption.Params
|
||||||
CopiesNumbers []uint32
|
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) {
|
func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.ExtendedObjectInfo, error) {
|
||||||
if p.StartByte == 0 || p.StartByte == p.SrcSize {
|
if p.Range.Start == p.SrcSize {
|
||||||
var r io.Reader
|
|
||||||
|
|
||||||
objPayload, err := n.GetObject(ctx, &GetObjectParams{
|
objPayload, err := n.GetObject(ctx, &GetObjectParams{
|
||||||
ObjectInfo: p.Object,
|
ObjectInfo: p.Object,
|
||||||
Versioned: true,
|
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)
|
return nil, fmt.Errorf("get object to patch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.StartByte == 0 {
|
return n.PutObject(ctx, &PutObjectParams{
|
||||||
r = io.MultiReader(p.NewBytes, objPayload)
|
BktInfo: p.BktInfo,
|
||||||
} else {
|
Object: p.Object.Name,
|
||||||
r = io.MultiReader(objPayload, p.NewBytes)
|
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{
|
return n.PutObject(ctx, &PutObjectParams{
|
||||||
BktInfo: p.BktInfo,
|
BktInfo: p.BktInfo,
|
||||||
Object: p.Object.Name,
|
Object: p.Object.Name,
|
||||||
Size: p.DstSize,
|
Size: p.SrcSize - 1 - p.Range.End + p.NewBytesSize,
|
||||||
Reader: r,
|
Reader: io.MultiReader(p.NewBytes, objPayload),
|
||||||
Header: p.Header,
|
Header: p.Header,
|
||||||
Encryption: p.Encryption,
|
Encryption: p.Encryption,
|
||||||
CopiesNumbers: p.CopiesNumbers,
|
CopiesNumbers: p.CopiesNumbers,
|
||||||
|
@ -687,7 +714,7 @@ func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
|
||||||
|
|
||||||
objPayload1, err := n.GetObject(ctx, &GetObjectParams{
|
objPayload1, err := n.GetObject(ctx, &GetObjectParams{
|
||||||
ObjectInfo: p.Object,
|
ObjectInfo: p.Object,
|
||||||
Range: &RangeParams{Start: 0, End: p.StartByte - 1},
|
Range: &RangeParams{Start: 0, End: p.Range.Start - 1},
|
||||||
Versioned: true,
|
Versioned: true,
|
||||||
BucketInfo: p.BktInfo,
|
BucketInfo: p.BktInfo,
|
||||||
Encryption: p.Encryption,
|
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)
|
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{
|
objPayload2, err := n.GetObject(ctx, &GetObjectParams{
|
||||||
ObjectInfo: p.Object,
|
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,
|
Versioned: true,
|
||||||
BucketInfo: p.BktInfo,
|
BucketInfo: p.BktInfo,
|
||||||
Encryption: p.Encryption,
|
Encryption: p.Encryption,
|
||||||
|
@ -710,7 +749,7 @@ func (n *layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.Ex
|
||||||
return n.PutObject(ctx, &PutObjectParams{
|
return n.PutObject(ctx, &PutObjectParams{
|
||||||
BktInfo: p.BktInfo,
|
BktInfo: p.BktInfo,
|
||||||
Object: p.Object.Name,
|
Object: p.Object.Name,
|
||||||
Size: p.DstSize,
|
Size: p.SrcSize,
|
||||||
Reader: io.MultiReader(objPayload1, p.NewBytes, objPayload2),
|
Reader: io.MultiReader(objPayload1, p.NewBytes, objPayload2),
|
||||||
Header: p.Header,
|
Header: p.Header,
|
||||||
Encryption: p.Encryption,
|
Encryption: p.Encryption,
|
||||||
|
|
Loading…
Reference in a new issue