| @@ -113,7 +113,7 @@ func (t *CreatePackage) Execute(task *task.Task[TaskContext], ctx TaskContext, c | |||||
| path := filepath.ToSlash(obj.Path) | path := filepath.ToSlash(obj.Path) | ||||
| // 上传对象 | // 上传对象 | ||||
| err = up.Upload(path, obj.Size, obj.File) | |||||
| err = up.Upload(path, obj.File) | |||||
| if err != nil { | if err != nil { | ||||
| err = fmt.Errorf("uploading object: %w", err) | err = fmt.Errorf("uploading object: %w", err) | ||||
| log.Error(err.Error()) | log.Error(err.Error()) | ||||
| @@ -48,17 +48,13 @@ var _ = MustAddCmd(func(ctx CommandContext, packageID cdssdk.PackageID, rootPath | |||||
| return nil | return nil | ||||
| } | } | ||||
| info, err := fi.Info() | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| file, err := os.Open(fname) | file, err := os.Open(fname) | ||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| defer file.Close() | defer file.Close() | ||||
| return up.Upload(fname, info.Size(), file) | |||||
| return up.Upload(fname, file) | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| @@ -100,7 +100,7 @@ func init() { | |||||
| } | } | ||||
| defer file.Close() | defer file.Close() | ||||
| return up.Upload(fname, info.Size(), file) | |||||
| return up.Upload(fname, file) | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| fmt.Println(err.Error()) | fmt.Println(err.Error()) | ||||
| @@ -8,6 +8,7 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "net/http" | "net/http" | ||||
| "strconv" | |||||
| "strings" | "strings" | ||||
| "time" | "time" | ||||
| @@ -93,7 +94,7 @@ func (a *AWSAuth) Auth(c *gin.Context) { | |||||
| return | return | ||||
| } | } | ||||
| verifySig := a.getSignature(verifyReq) | |||||
| verifySig := getSignatureFromAWSHeader(verifyReq) | |||||
| if !strings.EqualFold(verifySig, reqSig) { | if !strings.EqualFold(verifySig, reqSig) { | ||||
| c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch")) | c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch")) | ||||
| return | return | ||||
| @@ -135,13 +136,14 @@ func (a *AWSAuth) AuthWithoutBody(c *gin.Context) { | |||||
| } | } | ||||
| err = a.signer.SignHTTP(context.TODO(), a.cred, verifyReq, "", AuthService, AuthRegion, timestamp) | err = a.signer.SignHTTP(context.TODO(), a.cred, verifyReq, "", AuthService, AuthRegion, timestamp) | ||||
| if err != nil { | if err != nil { | ||||
| logger.Warnf("sign request: %v", err) | logger.Warnf("sign request: %v", err) | ||||
| c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, "sign request failed")) | c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, "sign request failed")) | ||||
| return | return | ||||
| } | } | ||||
| verifySig := a.getSignature(verifyReq) | |||||
| verifySig := getSignatureFromAWSHeader(verifyReq) | |||||
| if strings.EqualFold(verifySig, reqSig) { | if strings.EqualFold(verifySig, reqSig) { | ||||
| c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch")) | c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch")) | ||||
| return | return | ||||
| @@ -150,6 +152,68 @@ func (a *AWSAuth) AuthWithoutBody(c *gin.Context) { | |||||
| c.Next() | c.Next() | ||||
| } | } | ||||
| func (a *AWSAuth) PresignedAuth(c *gin.Context) { | |||||
| query := c.Request.URL.Query() | |||||
| signature := query.Get("X-Amz-Signature") | |||||
| query.Del("X-Amz-Signature") | |||||
| if signature == "" { | |||||
| c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing X-Amz-Signature query parameter")) | |||||
| return | |||||
| } | |||||
| // alg := c.Request.URL.Query().Get("X-Amz-Algorithm") | |||||
| // cred := c.Request.URL.Query().Get("X-Amz-Credential") | |||||
| date := query.Get("X-Amz-Date") | |||||
| expiresStr := query.Get("X-Expires") | |||||
| expires, err := strconv.ParseInt(expiresStr, 10, 64) | |||||
| if err != nil { | |||||
| c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "invalid X-Expires format")) | |||||
| return | |||||
| } | |||||
| signedHeaders := strings.Split(query.Get("X-Amz-SignedHeaders"), ";") | |||||
| c.Request.URL.RawQuery = query.Encode() | |||||
| verifyReq, err := http.NewRequest(c.Request.Method, c.Request.URL.String(), nil) | |||||
| if err != nil { | |||||
| c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, err.Error())) | |||||
| return | |||||
| } | |||||
| for _, h := range signedHeaders { | |||||
| verifyReq.Header.Add(h, c.Request.Header.Get(h)) | |||||
| } | |||||
| verifyReq.Host = c.Request.Host | |||||
| timestamp, err := time.Parse("20060102T150405Z", date) | |||||
| if err != nil { | |||||
| c.AbortWithStatusJSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "invalid X-Amz-Date format")) | |||||
| return | |||||
| } | |||||
| if time.Now().After(timestamp.Add(time.Duration(expires) * time.Second)) { | |||||
| c.AbortWithStatusJSON(http.StatusUnauthorized, Failed(errorcode.Unauthorized, "request expired")) | |||||
| return | |||||
| } | |||||
| signer := v4.NewSigner() | |||||
| uri, _, err := signer.PresignHTTP(context.TODO(), a.cred, verifyReq, "", AuthService, AuthRegion, timestamp) | |||||
| if err != nil { | |||||
| c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.OperationFailed, "sign request failed")) | |||||
| return | |||||
| } | |||||
| verifySig := getSignatureFromAWSQuery(uri) | |||||
| if !strings.EqualFold(verifySig, signature) { | |||||
| c.AbortWithStatusJSON(http.StatusOK, Failed(errorcode.Unauthorized, "signature mismatch")) | |||||
| return | |||||
| } | |||||
| c.Next() | |||||
| } | |||||
| // 解析 Authorization 头部 | // 解析 Authorization 头部 | ||||
| func parseAuthorizationHeader(authorizationHeader string) (string, []string, string, error) { | func parseAuthorizationHeader(authorizationHeader string) (string, []string, string, error) { | ||||
| if !strings.HasPrefix(authorizationHeader, "AWS4-HMAC-SHA256 ") { | if !strings.HasPrefix(authorizationHeader, "AWS4-HMAC-SHA256 ") { | ||||
| @@ -186,7 +250,7 @@ func parseAuthorizationHeader(authorizationHeader string) (string, []string, str | |||||
| return credential, headers, signature, nil | return credential, headers, signature, nil | ||||
| } | } | ||||
| func (a *AWSAuth) getSignature(req *http.Request) string { | |||||
| func getSignatureFromAWSHeader(req *http.Request) string { | |||||
| auth := req.Header.Get(AuthorizationHeader) | auth := req.Header.Get(AuthorizationHeader) | ||||
| idx := strings.Index(auth, "Signature=") | idx := strings.Index(auth, "Signature=") | ||||
| if idx == -1 { | if idx == -1 { | ||||
| @@ -195,3 +259,17 @@ func (a *AWSAuth) getSignature(req *http.Request) string { | |||||
| return auth[idx+len("Signature="):] | return auth[idx+len("Signature="):] | ||||
| } | } | ||||
| func getSignatureFromAWSQuery(uri string) string { | |||||
| idx := strings.Index(uri, "X-Amz-Signature=") | |||||
| if idx == -1 { | |||||
| return "" | |||||
| } | |||||
| andIdx := strings.Index(uri[idx:], "&") | |||||
| if andIdx == -1 { | |||||
| return uri[idx+len("X-Amz-Signature="):] | |||||
| } | |||||
| return uri[idx+len("X-Amz-Signature=") : andIdx] | |||||
| } | |||||
| @@ -109,7 +109,7 @@ func (s *ObjectService) Upload(ctx *gin.Context) { | |||||
| } | } | ||||
| path = filepath.ToSlash(path) | path = filepath.ToSlash(path) | ||||
| err = up.Upload(path, file.Size, f) | |||||
| err = up.Upload(path, f) | |||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("uploading file: %s", err.Error()) | log.Warnf("uploading file: %s", err.Error()) | ||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.Filename, err))) | ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("uploading file %v: %v", file.Filename, err))) | ||||
| @@ -0,0 +1,180 @@ | |||||
| package http | |||||
| import ( | |||||
| "fmt" | |||||
| "io" | |||||
| "net/http" | |||||
| "net/url" | |||||
| "path" | |||||
| "path/filepath" | |||||
| "github.com/gin-gonic/gin" | |||||
| "gitlink.org.cn/cloudream/common/consts/errorcode" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||||
| "gitlink.org.cn/cloudream/common/sdks/storage/cdsapi" | |||||
| "gitlink.org.cn/cloudream/common/utils/math2" | |||||
| "gitlink.org.cn/cloudream/storage/client/internal/config" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" | |||||
| ) | |||||
| type PresignedService struct { | |||||
| *Server | |||||
| } | |||||
| func (s *Server) Presigned() *PresignedService { | |||||
| return &PresignedService{ | |||||
| Server: s, | |||||
| } | |||||
| } | |||||
| func (s *PresignedService) ObjectDownloadByPath(ctx *gin.Context) { | |||||
| log := logger.WithField("HTTP", "Presigned.ObjectDownloadByPath") | |||||
| var req cdsapi.PresignedObjectDownloadByPath | |||||
| if err := ctx.ShouldBindQuery(&req); err != nil { | |||||
| log.Warnf("binding query: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) | |||||
| return | |||||
| } | |||||
| _, obj, err := s.svc.ObjectSvc().GetByPath(req.UserID, req.PackageID, req.Path, false, false) | |||||
| if err != nil { | |||||
| log.Warnf("getting object by path: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, "get object by path failed")) | |||||
| return | |||||
| } | |||||
| if len(obj) == 0 { | |||||
| log.Warnf("object not found: %s", req.Path) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.DataNotFound, "object not found")) | |||||
| return | |||||
| } | |||||
| off := req.Offset | |||||
| len := int64(-1) | |||||
| if req.Length != nil { | |||||
| len = *req.Length | |||||
| } | |||||
| file, err := s.svc.ObjectSvc().Download(req.UserID, downloader.DownloadReqeust{ | |||||
| ObjectID: obj[0].ObjectID, | |||||
| Offset: off, | |||||
| Length: len, | |||||
| }) | |||||
| if err != nil { | |||||
| log.Warnf("downloading object: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, "download object failed")) | |||||
| return | |||||
| } | |||||
| defer file.File.Close() | |||||
| ctx.Header("Content-Disposition", "attachment; filename="+url.PathEscape(path.Base(file.Object.Path))) | |||||
| ctx.Header("Content-Type", "application/octet-stream") | |||||
| ctx.Header("Content-Transfer-Encoding", "binary") | |||||
| n, err := io.Copy(ctx.Writer, file.File) | |||||
| if err != nil { | |||||
| log.Warnf("copying file: %s", err.Error()) | |||||
| } | |||||
| if config.Cfg().StorageID > 0 { | |||||
| s.svc.AccessStat.AddAccessCounter(file.Object.ObjectID, file.Object.PackageID, config.Cfg().StorageID, math2.DivOrDefault(float64(n), float64(file.Object.Size), 1)) | |||||
| } | |||||
| } | |||||
| func (s *PresignedService) ObjectUpload(ctx *gin.Context) { | |||||
| log := logger.WithField("HTTP", "Presigned.ObjectUpload") | |||||
| var req cdsapi.PresignedObjectUpload | |||||
| if err := ctx.ShouldBindQuery(&req); err != nil { | |||||
| log.Warnf("binding query: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) | |||||
| return | |||||
| } | |||||
| up, err := s.svc.Uploader.BeginUpdate(req.UserID, req.PackageID, req.Affinity, req.LoadTo, req.LoadToPath) | |||||
| if err != nil { | |||||
| log.Warnf("begin update: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("begin update: %v", err))) | |||||
| return | |||||
| } | |||||
| defer up.Abort() | |||||
| path := filepath.ToSlash(req.Path) | |||||
| err = up.Upload(path, ctx.Request.Body) | |||||
| if err != nil { | |||||
| log.Warnf("uploading file: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("uploading file %v: %v", req.Path, err))) | |||||
| return | |||||
| } | |||||
| ret, err := up.Commit() | |||||
| if err != nil { | |||||
| log.Warnf("commit update: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("commit update: %v", err))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, OK(cdsapi.PresignedObjectUploadResp{Object: ret.Objects[path]})) | |||||
| } | |||||
| func (s *PresignedService) ObjectNewMultipartUpload(ctx *gin.Context) { | |||||
| log := logger.WithField("HTTP", "Presigned.ObjectNewMultipartUpload") | |||||
| var req cdsapi.PresignedObjectNewMultipartUpload | |||||
| if err := ctx.ShouldBindQuery(&req); err != nil { | |||||
| log.Warnf("binding query: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) | |||||
| return | |||||
| } | |||||
| obj, err := s.svc.ObjectSvc().NewMultipartUploadObject(req.UserID, req.PackageID, req.Path) | |||||
| if err != nil { | |||||
| log.Warnf("new multipart upload: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("new multipart upload: %v", err))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, OK(cdsapi.PresignedObjectUploadResp{Object: obj})) | |||||
| } | |||||
| func (s *PresignedService) ObjectUploadPart(ctx *gin.Context) { | |||||
| log := logger.WithField("HTTP", "Presigned.ObjectUploadPart") | |||||
| var req cdsapi.PresignedObjectUploadPart | |||||
| if err := ctx.ShouldBindQuery(&req); err != nil { | |||||
| log.Warnf("binding query: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) | |||||
| return | |||||
| } | |||||
| err := s.svc.Uploader.UploadPart(req.UserID, req.ObjectID, req.Index, ctx.Request.Body) | |||||
| if err != nil { | |||||
| log.Warnf("uploading part: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("upload part: %v", err))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, OK(cdsapi.ObjectUploadPartResp{})) | |||||
| } | |||||
| func (s *PresignedService) ObjectCompleteMultipartUpload(ctx *gin.Context) { | |||||
| log := logger.WithField("HTTP", "Presigned.ObjectCompleteMultipartUpload") | |||||
| var req cdsapi.PresignedObjectCompleteMultipartUpload | |||||
| if err := ctx.ShouldBindQuery(&req); err != nil { | |||||
| log.Warnf("binding query: %s", err.Error()) | |||||
| ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) | |||||
| return | |||||
| } | |||||
| obj, err := s.svc.ObjectSvc().CompleteMultipartUpload(req.UserID, req.ObjectID, req.Indexes) | |||||
| if err != nil { | |||||
| log.Warnf("completing multipart upload: %s", err.Error()) | |||||
| ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, fmt.Sprintf("complete multipart upload: %v", err))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, OK(cdsapi.ObjectCompleteMultipartUploadResp{Object: obj})) | |||||
| } | |||||
| @@ -123,4 +123,11 @@ func (s *Server) routeV1(eg *gin.Engine, rt gin.IRoutes) { | |||||
| rt.POST(cdsapi.ObjectNewMultipartUploadPath, s.Object().NewMultipartUpload) | rt.POST(cdsapi.ObjectNewMultipartUploadPath, s.Object().NewMultipartUpload) | ||||
| rt.POST(cdsapi.ObjectUploadPartPath, s.Object().UploadPart) | rt.POST(cdsapi.ObjectUploadPartPath, s.Object().UploadPart) | ||||
| rt.POST(cdsapi.ObjectCompleteMultipartUploadPath, s.Object().CompleteMultipartUpload) | rt.POST(cdsapi.ObjectCompleteMultipartUploadPath, s.Object().CompleteMultipartUpload) | ||||
| rt.GET(cdsapi.PresignedObjectDownloadByPathPath, s.awsAuth.PresignedAuth, s.Presigned().ObjectDownloadByPath) | |||||
| rt.POST(cdsapi.PresignedObjectUploadPath, s.awsAuth.PresignedAuth, s.Presigned().ObjectUpload) | |||||
| rt.POST(cdsapi.PresignedObjectNewMultipartUploadPath, s.awsAuth.PresignedAuth, s.Presigned().ObjectNewMultipartUpload) | |||||
| rt.POST(cdsapi.PresignedObjectUploadPartPath, s.awsAuth.PresignedAuth, s.Presigned().ObjectUploadPart) | |||||
| rt.POST(cdsapi.PresignedObjectCompleteMultipartUploadPath, s.awsAuth.PresignedAuth, s.Presigned().ObjectCompleteMultipartUpload) | |||||
| } | } | ||||
| @@ -42,13 +42,13 @@ type UpdateResult struct { | |||||
| Objects map[string]cdssdk.Object | Objects map[string]cdssdk.Object | ||||
| } | } | ||||
| func (w *UpdateUploader) Upload(pat string, size int64, stream io.Reader) error { | |||||
| func (w *UpdateUploader) Upload(pat string, stream io.Reader) error { | |||||
| uploadTime := time.Now() | uploadTime := time.Now() | ||||
| ft := ioswitch2.NewFromTo() | ft := ioswitch2.NewFromTo() | ||||
| fromExec, hd := ioswitch2.NewFromDriver(ioswitch2.RawStream()) | fromExec, hd := ioswitch2.NewFromDriver(ioswitch2.RawStream()) | ||||
| ft.AddFrom(fromExec). | ft.AddFrom(fromExec). | ||||
| AddTo(ioswitch2.NewToShardStore(*w.targetStg.MasterHub, w.targetStg, ioswitch2.RawStream(), "fileHash")) | |||||
| AddTo(ioswitch2.NewToShardStore(*w.targetStg.MasterHub, w.targetStg, ioswitch2.RawStream(), "shardInfo")) | |||||
| for i, stg := range w.loadToStgs { | for i, stg := range w.loadToStgs { | ||||
| ft.AddTo(ioswitch2.NewLoadToPublic(*stg.MasterHub, stg, path.Join(w.loadToPath[i], pat))) | ft.AddTo(ioswitch2.NewLoadToPublic(*stg.MasterHub, stg, path.Join(w.loadToPath[i], pat))) | ||||
| @@ -73,10 +73,11 @@ func (w *UpdateUploader) Upload(pat string, size int64, stream io.Reader) error | |||||
| defer w.lock.Unlock() | defer w.lock.Unlock() | ||||
| // 记录上传结果 | // 记录上传结果 | ||||
| shardInfo := ret["shardInfo"].(*ops2.ShardInfoValue) | |||||
| w.successes = append(w.successes, coormq.AddObjectEntry{ | w.successes = append(w.successes, coormq.AddObjectEntry{ | ||||
| Path: pat, | Path: pat, | ||||
| Size: size, | |||||
| FileHash: ret["fileHash"].(*ops2.ShardInfoValue).Hash, | |||||
| Size: shardInfo.Size, | |||||
| FileHash: shardInfo.Hash, | |||||
| UploadTime: uploadTime, | UploadTime: uploadTime, | ||||
| StorageIDs: []cdssdk.StorageID{w.targetStg.Storage.StorageID}, | StorageIDs: []cdssdk.StorageID{w.targetStg.Storage.StorageID}, | ||||
| }) | }) | ||||