package handler import ( "errors" "io" "strconv" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/multipart" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "go.uber.org/zap" ) const attributeMultipartObjectSize = "S3-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)) if err = part.Close(); err != nil { l.Warn(logs.FailedToCloseReader, zap.Error(err)) } 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 getMultiobjectBodyParams) (io.ReadCloser, uint64, error) { 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(p.strSize, 10, 64) if err != nil { return nil, 0, err } ctx := p.req.RequestCtx params := PrmInitMultiObjectReader{ Addr: newAddress(cid, oid), Bearer: bearerToken(ctx), } payload, err := h.frostfs.InitMultiObjectReader(ctx, params) if err != nil { return nil, 0, err } return io.NopCloser(payload), size, nil }