frostfs-http-gw/internal/handler/multipart.go
Nikita Zinkevich 0b03f693a2
All checks were successful
/ Vulncheck (pull_request) Successful in 57s
/ DCO (pull_request) Successful in 1m13s
/ Builds (pull_request) Successful in 1m10s
/ Lint (pull_request) Successful in 2m13s
/ Tests (pull_request) Successful in 1m17s
[#142] Fix multipart-objects download
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2024-10-07 14:38:28 +03:00

97 lines
2.4 KiB
Go

package handler
import (
"errors"
"io"
"strconv"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/multipart"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"go.uber.org/zap"
)
const (
frostFSSystemMetadataPrefix = "S3-"
attributeMultipartObjectSize = frostFSSystemMetadataPrefix + "Multipart-Object-Size"
)
// MultipartFile provides standard ReadCloser interface and also allows one to
// get file name, it's used for multipart uploads.
type MultipartFile interface {
io.ReadCloser
FileName() string
}
func fetchMultipartFile(l *zap.Logger, r io.Reader, boundary string) (MultipartFile, error) {
// To have a custom buffer (3mb) the custom multipart reader is used.
// Default reader uses 4KiB chunks, which slow down upload speed up to 400%
// https://github.com/golang/go/blob/91b9915d3f6f8cd2e9e9fda63f67772803adfa03/src/mime/multipart/multipart.go#L32
reader := multipart.NewReader(r, boundary)
for {
part, err := reader.NextPart()
if err != nil {
return nil, err
}
name := part.FormName()
if name == "" {
l.Debug(logs.IgnorePartEmptyFormName)
continue
}
filename := part.FileName()
// ignore multipart/form-data values
if filename == "" {
l.Debug(logs.IgnorePartEmptyFilename, zap.String("form", name))
continue
}
return part, nil
}
}
// getPayload returns initial payload if object is not multipart else composes new reader with parts data.
func (h *Handler) getPayload(p getPayloadParams) (io.ReadCloser, uint64, error) {
sizeValue, ok := p.attrs[attributeMultipartObjectSize]
if !ok {
return p.obj.Payload, p.obj.Header.PayloadSize(), nil
}
cid, ok := p.obj.Header.ContainerID()
if !ok {
return nil, 0, errors.New("no container id set")
}
oid, ok := p.obj.Header.ID()
if !ok {
return nil, 0, errors.New("no object id set")
}
size, err := strconv.ParseUint(sizeValue, 10, 64)
if err != nil {
return nil, 0, err
}
ctx := p.req.RequestCtx
params := PrmInitMultiObjectReader{
Off: 0,
Ln: 0,
ObjInfo: &data.ObjectInfo{
ID: oid,
CID: cid,
Bucket: p.bktinfo.Name,
Name: p.attrs["FilePath"],
Size: size,
Headers: p.attrs,
},
BktInfo: p.bktinfo,
Log: h.log,
Bearer: bearerToken(ctx),
}
payload, err := h.frostfs.InitMultiObjectReader(ctx, params)
if err != nil {
return nil, 0, err
}
return io.NopCloser(payload), size, nil
}