diff --git a/sdks/storage/cdsapi/object.go b/sdks/storage/cdsapi/object.go index 80fc18f..9b4b8ed 100644 --- a/sdks/storage/cdsapi/object.go +++ b/sdks/storage/cdsapi/object.go @@ -1,6 +1,7 @@ package cdsapi import ( + "context" "fmt" "io" "mime" @@ -9,6 +10,8 @@ import ( "strings" "time" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/credentials" "gitlink.org.cn/cloudream/common/consts/errorcode" "gitlink.org.cn/cloudream/common/pkgs/iterator" "gitlink.org.cn/cloudream/common/sdks" @@ -115,16 +118,15 @@ func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { return nil, fmt.Errorf("upload info to json: %w", err) } - resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{ - Form: map[string]string{"info": string(infoJSON)}, - Files: iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { + resp, err := PostMultiPart(c.cfg, url, + map[string]string{"info": string(infoJSON)}, + iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { return &http2.IterMultiPartFile{ FieldName: "files", FileName: src.Path, File: src.File, }, nil - }), - }) + })) if err != nil { return nil, err } @@ -151,25 +153,42 @@ func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) { const ObjectDownloadPath = "/object/download" type ObjectDownload struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - ObjectID cdssdk.ObjectID `form:"objectID" json:"objectID" binding:"required"` - Offset int64 `form:"offset" json:"offset,omitempty"` - Length *int64 `form:"length" json:"length,omitempty"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + ObjectID cdssdk.ObjectID `form:"objectID" url:"objectID" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (r *ObjectDownload) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadPath, r) } + type DownloadingObject struct { Path string File io.ReadCloser } func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) { - url, err := url.JoinPath(c.cfg.URL, ObjectDownloadPath) + httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL) if err != nil { return nil, err } - resp, err := http2.GetJSON(url, http2.RequestParam{ - Query: req, - }) + if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" { + prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return nil, err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now()) + if err != nil { + return nil, err + } + } + + resp, err := http.DefaultClient.Do(httpReq) if err != nil { return nil, err } @@ -199,22 +218,38 @@ func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) const ObjectDownloadByPathPath = "/object/downloadByPath" type ObjectDownloadByPath struct { - UserID cdssdk.UserID `form:"userID" json:"userID" binding:"required"` - PackageID cdssdk.PackageID `form:"packageID" json:"packageID" binding:"required"` - Path string `form:"path" json:"path" binding:"required"` - Offset int64 `form:"offset" json:"offset,omitempty"` - Length *int64 `form:"length" json:"length,omitempty"` + UserID cdssdk.UserID `form:"userID" url:"userID" binding:"required"` + PackageID cdssdk.PackageID `form:"packageID" url:"packageID" binding:"required"` + Path string `form:"path" url:"path" binding:"required"` + Offset int64 `form:"offset" url:"offset,omitempty"` + Length *int64 `form:"length" url:"length,omitempty"` +} + +func (r *ObjectDownloadByPath) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadByPathPath, r) } func (c *ObjectService) DownloadByPath(req ObjectDownloadByPath) (*DownloadingObject, error) { - url, err := url.JoinPath(c.cfg.URL, ObjectDownloadByPathPath) + httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL) if err != nil { return nil, err } - resp, err := http2.GetJSON(url, http2.RequestParam{ - Query: req, - }) + if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" { + prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return nil, err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now()) + if err != nil { + return nil, err + } + } + + resp, err := http.DefaultClient.Do(httpReq) if err != nil { return nil, err } diff --git a/sdks/storage/cdsapi/package.go b/sdks/storage/cdsapi/package.go index 085871f..621f64b 100644 --- a/sdks/storage/cdsapi/package.go +++ b/sdks/storage/cdsapi/package.go @@ -121,16 +121,15 @@ func (c *PackageService) CreateLoad(req PackageCreateLoad) (*PackageCreateLoadRe return nil, fmt.Errorf("upload info to json: %w", err) } - resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{ - Form: map[string]string{"info": string(infoJSON)}, - Files: iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { + resp, err := PostMultiPart(c.cfg, url, + map[string]string{"info": string(infoJSON)}, + iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) { return &http2.IterMultiPartFile{ FieldName: "files", FileName: src.Path, File: src.File, }, nil - }), - }) + })) if err != nil { return nil, err } diff --git a/sdks/storage/cdsapi/utils.go b/sdks/storage/cdsapi/utils.go index 079d50e..5c573ae 100644 --- a/sdks/storage/cdsapi/utils.go +++ b/sdks/storage/cdsapi/utils.go @@ -2,15 +2,21 @@ package cdsapi import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "io" + "mime/multipart" "net/http" + ul "net/url" "path/filepath" "strings" "time" v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/google/go-querystring/query" + "gitlink.org.cn/cloudream/common/pkgs/iterator" "gitlink.org.cn/cloudream/common/sdks" "gitlink.org.cn/cloudream/common/utils/http2" "gitlink.org.cn/cloudream/common/utils/math2" @@ -60,7 +66,7 @@ func JSONAPI[Resp sdks.APIResponse, Req sdks.APIRequest](cfg *Config, cli *http. } signer := v4.NewSigner() - err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now()) + err = signer.SignHTTP(context.Background(), cred, httpReq, calcSha256(param.Body), AuthService, AuthRegion, time.Now()) if err != nil { return re, err } @@ -91,7 +97,7 @@ func JSONAPINoData[Resp sdks.APIResponse, Req sdks.APIRequest](cfg *Config, cli } signer := v4.NewSigner() - err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now()) + err = signer.SignHTTP(context.Background(), cred, httpReq, calcSha256(param.Body), AuthService, AuthRegion, time.Now()) if err != nil { return err } @@ -104,3 +110,108 @@ func JSONAPINoData[Resp sdks.APIResponse, Req sdks.APIRequest](cfg *Config, cli return sdks.ParseCodeDataJSONResponse(resp, any(nil)) } + +func calcSha256(body sdks.RequestBody) string { + hasher := sha256.New() + switch body := body.(type) { + case *sdks.StringBody: + return hex.EncodeToString(hasher.Sum([]byte(body.Value))) + + case *sdks.BytesBody: + return hex.EncodeToString(hasher.Sum(body.Value)) + + default: + return "" + } +} + +func PostMultiPart(cfg *Config, url string, info any, files http2.MultiPartFileIterator) (*http.Response, error) { + req, err := http.NewRequest(http.MethodPost, url, nil) + if err != nil { + return nil, err + } + + pr, pw := io.Pipe() + muWriter := multipart.NewWriter(pw) + + req.Header.Set("Content-Type", fmt.Sprintf("%s;boundary=%s", http2.ContentTypeMultiPart, muWriter.Boundary())) + + writeResult := make(chan error, 1) + go func() { + writeResult <- func() error { + defer pw.Close() + defer muWriter.Close() + + if info != nil { + mp, err := query.Values(info) + if err != nil { + return fmt.Errorf("formValues object to map failed, err: %w", err) + } + + for k, v := range mp { + err := muWriter.WriteField(k, v[0]) + if err != nil { + return fmt.Errorf("write form field failed, err: %w", err) + } + } + } + + for { + file, err := files.MoveNext() + if err == iterator.ErrNoMoreItem { + break + } + if err != nil { + return fmt.Errorf("opening file: %w", err) + } + + err = sendFileOnePart(muWriter, file.FieldName, file.FileName, file.File) + file.File.Close() + if err != nil { + return err + } + } + + return nil + }() + }() + + req.Body = pr + + if cfg.AccessKey != "" && cfg.SecretKey != "" { + prod := credentials.NewStaticCredentialsProvider(cfg.AccessKey, cfg.SecretKey, "") + cred, err := prod.Retrieve(context.TODO()) + if err != nil { + return nil, err + } + + signer := v4.NewSigner() + err = signer.SignHTTP(context.Background(), cred, req, "", AuthService, AuthRegion, time.Now()) + if err != nil { + return nil, err + } + } + + cli := http.Client{} + resp, err := cli.Do(req) + if err != nil { + return nil, err + } + + writeErr := <-writeResult + if writeErr != nil { + return nil, writeErr + } + + return resp, nil +} + +func sendFileOnePart(muWriter *multipart.Writer, fieldName, fileName string, file io.ReadCloser) error { + w, err := muWriter.CreateFormFile(fieldName, ul.PathEscape(fileName)) + if err != nil { + return fmt.Errorf("create form file failed, err: %w", err) + } + + _, err = io.Copy(w, file) + return err +}