github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/uploader/uploaders.go (about) 1 package uploader 2 3 import ( 4 "bytes" 5 "context" 6 stderrors "errors" 7 "hash/crc32" 8 "io" 9 "os" 10 "strings" 11 "sync" 12 "time" 13 14 internal_io "github.com/qiniu/go-sdk/v7/internal/io" 15 "github.com/qiniu/go-sdk/v7/storagev2/apis" 16 creds "github.com/qiniu/go-sdk/v7/storagev2/credentials" 17 "github.com/qiniu/go-sdk/v7/storagev2/errors" 18 httpclient "github.com/qiniu/go-sdk/v7/storagev2/http_client" 19 "github.com/qiniu/go-sdk/v7/storagev2/region" 20 "github.com/qiniu/go-sdk/v7/storagev2/retrier" 21 "github.com/qiniu/go-sdk/v7/storagev2/uploader/source" 22 "github.com/qiniu/go-sdk/v7/storagev2/uptoken" 23 "modernc.org/fileutil" 24 ) 25 26 type ( 27 // 表单上传器选项 28 FormUploaderOptions struct { 29 httpclient.Options 30 31 // 上传凭证 32 UpToken uptoken.Provider 33 } 34 35 formUploader struct { 36 storage *apis.Storage 37 options *FormUploaderOptions 38 } 39 40 multiPartsUploader struct { 41 scheduler multiPartsUploaderScheduler 42 } 43 ) 44 45 // 创建表单上传器 46 func NewFormUploader(options *FormUploaderOptions) Uploader { 47 if options == nil { 48 options = &FormUploaderOptions{} 49 } 50 return formUploader{apis.NewStorage(&options.Options), options} 51 } 52 53 func (uploader formUploader) UploadFile(ctx context.Context, path string, objectOptions *ObjectOptions, returnValue interface{}) error { 54 if objectOptions == nil { 55 objectOptions = &ObjectOptions{} 56 } else { 57 tmp := *objectOptions 58 objectOptions = &tmp 59 } 60 61 upToken, err := getUpToken(uploader.options.Credentials, objectOptions, uploader.options.UpToken) 62 if err != nil { 63 return err 64 } 65 objectOptions.UpToken = upToken 66 67 file, err := os.Open(path) 68 if err != nil { 69 return err 70 } 71 defer file.Close() 72 73 fileInfo, err := file.Stat() 74 if err != nil { 75 return err 76 } 77 78 fileSize := uint64(fileInfo.Size()) 79 _ = fileutil.Fadvise(file, 0, 0, fileutil.POSIX_FADV_SEQUENTIAL) 80 81 crc32, err := crc32FromReadSeeker(file) 82 if err != nil { 83 return err 84 } 85 86 return uploader.upload(ctx, file, fileSize, crc32, returnValue, objectOptions) 87 } 88 89 func (uploader formUploader) UploadReader(ctx context.Context, reader io.Reader, objectOptions *ObjectOptions, returnValue interface{}) error { 90 var ( 91 rsc io.ReadSeeker 92 size uint64 93 ok bool 94 ) 95 if objectOptions == nil { 96 objectOptions = &ObjectOptions{} 97 } else { 98 tmp := *objectOptions 99 objectOptions = &tmp 100 } 101 102 upToken, err := getUpToken(uploader.options.Credentials, objectOptions, uploader.options.UpToken) 103 if err != nil { 104 return err 105 } 106 objectOptions.UpToken = upToken 107 108 if rsc, ok = reader.(io.ReadSeeker); ok && canSeekReally(rsc) { 109 if size, err = getSeekerSize(rsc); err != nil { 110 return err 111 } 112 } else { 113 dataBytes, err := internal_io.ReadAll(reader) 114 if err != nil { 115 return err 116 } 117 size = uint64(len(dataBytes)) 118 rsc = bytes.NewReader(dataBytes) 119 } 120 121 crc32, err := crc32FromReadSeeker(rsc) 122 if err != nil { 123 return err 124 } 125 126 return uploader.upload(ctx, rsc, size, crc32, returnValue, objectOptions) 127 } 128 129 func (uploader formUploader) upload(ctx context.Context, reader io.ReadSeeker, size uint64, crc32 uint32, returnValue interface{}, objectOptions *ObjectOptions) error { 130 return forEachRegion(ctx, objectOptions, &uploader.options.Options, func(region *region.Region) (bool, error) { 131 err := uploader.uploadToRegion(ctx, region, reader, size, crc32, returnValue, objectOptions) 132 return true, err 133 }) 134 } 135 136 func (uploader formUploader) uploadToRegion(ctx context.Context, region *region.Region, reader io.ReadSeeker, size uint64, crc32 uint32, returnValue interface{}, objectOptions *ObjectOptions) error { 137 customData := mergeCustomVarsAndMetadata(objectOptions.Metadata, objectOptions.CustomVars) 138 options := apis.Options{OverwrittenRegion: region} 139 request := apis.PostObjectRequest{ 140 ObjectName: objectOptions.ObjectName, 141 UploadToken: objectOptions.UpToken, 142 Crc32: int64(crc32), 143 File: httpclient.MultipartFormBinaryData{ 144 Data: internal_io.NewReadSeekableNopCloser(reader), 145 Name: objectOptions.FileName, 146 ContentType: objectOptions.ContentType, 147 }, 148 CustomData: customData, 149 ResponseBody: returnValue, 150 } 151 if objectOptions.OnUploadingProgress != nil { 152 options.OnRequestProgress = func(uploaded, _ uint64) { 153 if uploaded > size { 154 uploaded = size 155 } 156 objectOptions.OnUploadingProgress(&UploadingProgress{Uploaded: uploaded, TotalSize: size}) 157 } 158 } 159 _, err := uploader.storage.PostObject(ctx, &request, &options) 160 return err 161 } 162 163 func newMultiPartsUploader(scheduler multiPartsUploaderScheduler) Uploader { 164 return multiPartsUploader{scheduler} 165 } 166 167 func (uploader multiPartsUploader) UploadFile(ctx context.Context, path string, objectOptions *ObjectOptions, returnValue interface{}) error { 168 if objectOptions == nil { 169 objectOptions = &ObjectOptions{} 170 } else { 171 tmp := *objectOptions 172 objectOptions = &tmp 173 } 174 175 options := uploader.scheduler.MultiPartsUploader().MultiPartsUploaderOptions() 176 if options == nil { 177 options = &MultiPartsUploaderOptions{} 178 } 179 180 upToken, err := getUpToken(options.Credentials, objectOptions, options.UpTokenProvider) 181 if err != nil { 182 return err 183 } 184 objectOptions.UpToken = upToken 185 186 src, err := source.NewFileSource(path) 187 if err != nil { 188 return err 189 } 190 defer src.Close() 191 192 if file := src.GetFile(); file != nil { 193 _ = fileutil.Fadvise(file, 0, 0, fileutil.POSIX_FADV_SEQUENTIAL) 194 } 195 196 return uploader.upload(ctx, src, &options.Options, objectOptions, returnValue) 197 } 198 199 func (uploader multiPartsUploader) UploadReader(ctx context.Context, reader io.Reader, objectOptions *ObjectOptions, returnValue interface{}) error { 200 if objectOptions == nil { 201 objectOptions = &ObjectOptions{} 202 } else { 203 tmp := *objectOptions 204 objectOptions = &tmp 205 } 206 207 options := uploader.scheduler.MultiPartsUploader().MultiPartsUploaderOptions() 208 if options == nil { 209 options = &MultiPartsUploaderOptions{} 210 } 211 212 upToken, err := getUpToken(options.Credentials, objectOptions, options.UpTokenProvider) 213 if err != nil { 214 return err 215 } 216 objectOptions.UpToken = upToken 217 218 var src source.Source 219 if rss, ok := reader.(io.ReadSeeker); ok && canSeekReally(rss) { 220 if rasc, ok := rss.(source.ReadAtSeekCloser); ok { 221 src = source.NewReadAtSeekCloserSource(rasc, "") 222 } else if rscs, ok := rss.(internal_io.ReadSeekCloser); ok { 223 src = source.NewReadSeekCloserSource(rscs, "") 224 } else { 225 src = source.NewReadSeekCloserSource(internal_io.MakeReadSeekCloserFromReader(rss), "") 226 } 227 } else { 228 src = source.NewReadCloserSource(io.NopCloser(reader), "") 229 } 230 231 return uploader.upload(ctx, src, &options.Options, objectOptions, returnValue) 232 } 233 234 func (uploader multiPartsUploader) upload(ctx context.Context, src source.Source, httpClientOptions *httpclient.Options, objectOptions *ObjectOptions, returnValue interface{}) error { 235 resumed, err := uploader.uploadResumedParts(ctx, src, objectOptions, returnValue) 236 if err == nil && resumed { 237 return nil 238 } else if resumed { 239 if rsrc, ok := src.(source.ResetableSource); ok { 240 if resetErr := rsrc.Reset(); resetErr == nil { 241 return err 242 } 243 } 244 } 245 return uploader.tryToUploadToEachRegion(ctx, src, httpClientOptions, objectOptions, returnValue) 246 } 247 248 func (uploader multiPartsUploader) uploadResumedParts(ctx context.Context, src source.Source, objectOptions *ObjectOptions, returnValue interface{}) (bool, error) { 249 multiPartsObjectOptions := MultiPartsObjectOptions{*objectOptions, uploader.scheduler.PartSize()} 250 if initializedParts := uploader.scheduler.MultiPartsUploader().TryToResume(ctx, src, &multiPartsObjectOptions); initializedParts == nil { 251 return false, nil 252 } else { 253 defer initializedParts.Close() 254 var size uint64 255 if ssrc, ok := src.(source.SizedSource); ok { 256 if totalSize, sizeErr := ssrc.TotalSize(); sizeErr == nil { 257 size = totalSize 258 } 259 } 260 if err := uploader.uploadPartsAndComplete(ctx, src, size, initializedParts, objectOptions, returnValue); err != nil { 261 return true, err 262 } else { 263 return true, nil 264 } 265 } 266 } 267 268 func (uploader multiPartsUploader) tryToUploadToEachRegion(ctx context.Context, src source.Source, httpClientOptions *httpclient.Options, objectOptions *ObjectOptions, returnValue interface{}) error { 269 return forEachRegion(ctx, objectOptions, httpClientOptions, func(region *region.Region) (bool, error) { 270 objectOptions.RegionsProvider = region 271 multiPartsObjectOptions := MultiPartsObjectOptions{*objectOptions, uploader.scheduler.PartSize()} 272 initializedParts, err := uploader.scheduler.MultiPartsUploader().InitializeParts(ctx, src, &multiPartsObjectOptions) 273 var size uint64 274 if ssrc, ok := src.(source.SizedSource); ok { 275 if totalSize, sizeErr := ssrc.TotalSize(); sizeErr == nil { 276 size = totalSize 277 } 278 } 279 if err == nil { 280 defer initializedParts.Close() 281 if err = uploader.uploadPartsAndComplete(ctx, src, size, initializedParts, objectOptions, returnValue); err == nil { 282 return true, nil 283 } 284 } 285 if rsrc, ok := src.(source.ResetableSource); ok { 286 if resetErr := rsrc.Reset(); resetErr == nil { 287 return true, err 288 } 289 } 290 return false, err 291 }) 292 } 293 294 func (uploader multiPartsUploader) uploadPartsAndComplete(ctx context.Context, src source.Source, size uint64, initializedParts InitializedParts, objectOptions *ObjectOptions, returnValue interface{}) error { 295 var uploadPartsOptions UploadPartsOptions 296 if objectOptions.OnUploadingProgress != nil { 297 progress := newUploadingPartsProgress() 298 uploadPartsOptions.OnUploadingProgress = func(partNumber uint64, p *UploadingPartProgress) { 299 progress.setPartUploadingProgress(partNumber, p.Uploaded) 300 objectOptions.OnUploadingProgress(&UploadingProgress{Uploaded: progress.totalUploaded(), TotalSize: size}) 301 } 302 uploadPartsOptions.OnPartUploaded = func(part UploadedPart) error { 303 progress.partUploaded(part.PartNumber(), part.PartSize()) 304 objectOptions.OnUploadingProgress(&UploadingProgress{Uploaded: progress.totalUploaded(), TotalSize: size}) 305 return nil 306 } 307 } 308 uploadParts, err := uploader.scheduler.UploadParts(ctx, initializedParts, src, &uploadPartsOptions) 309 if err != nil { 310 return err 311 } 312 return uploader.scheduler.MultiPartsUploader().CompleteParts(ctx, initializedParts, uploadParts, returnValue) 313 } 314 315 func getUpToken(c creds.CredentialsProvider, objectOptions *ObjectOptions, upTokenProvider uptoken.Provider) (uptoken.Provider, error) { 316 if objectOptions.UpToken != nil { 317 return objectOptions.UpToken, nil 318 } else if upTokenProvider != nil { 319 return upTokenProvider, nil 320 } else { 321 if c == nil { 322 c = creds.Default() 323 } 324 if c != nil && objectOptions.BucketName != "" { 325 return newCredentialsUpTokenSigner(c, objectOptions.BucketName, 1*time.Hour, 10*time.Minute), nil 326 } else { 327 return nil, errors.MissingRequiredFieldError{Name: "UpToken"} 328 } 329 } 330 } 331 332 func crc32FromReadSeeker(r io.ReadSeeker) (uint32, error) { 333 offset, err := r.Seek(0, io.SeekCurrent) 334 if err != nil { 335 return 0, err 336 } 337 hasher := crc32.NewIEEE() 338 if _, err = io.Copy(hasher, r); err != nil { 339 return 0, err 340 } 341 if _, err = r.Seek(offset, io.SeekStart); err != nil { 342 return 0, err 343 } 344 return hasher.Sum32(), nil 345 } 346 347 func mergeCustomVarsAndMetadata(metadata, customVars map[string]string) map[string]string { 348 result := make(map[string]string, len(metadata)+len(customVars)) 349 for k, v := range metadata { 350 result[normalizeMetadataKey(k)] = v 351 } 352 for k, v := range customVars { 353 result[normalizeCustomVarKey(k)] = v 354 } 355 return result 356 } 357 358 func normalizeMetadataKey(k string) string { 359 if !strings.HasPrefix(k, "x-qn-meta-") { 360 k = "x-qn-meta-" + k 361 } 362 return k 363 } 364 365 func normalizeCustomVarKey(k string) string { 366 if !strings.HasPrefix(k, "x:") { 367 k = "x:" + k 368 } 369 return k 370 } 371 372 func canSeekReally(seeker io.Seeker) bool { 373 _, err := seeker.Seek(0, io.SeekCurrent) 374 return err == nil 375 } 376 377 func getSeekerSize(seeker io.Seeker) (uint64, error) { 378 currentOffset, err := seeker.Seek(0, io.SeekCurrent) 379 if err != nil { 380 return 0, err 381 } 382 endOffset, err := seeker.Seek(0, io.SeekEnd) 383 if err != nil { 384 return 0, err 385 } 386 _, err = seeker.Seek(currentOffset, io.SeekStart) 387 if err != nil { 388 return 0, err 389 } 390 return uint64(endOffset - currentOffset), nil 391 } 392 393 func getRegions(ctx context.Context, upToken uptoken.Provider, bucketName string, options *httpclient.Options) (regions []*region.Region, err error) { 394 regionsProvider := options.Regions 395 if regionsProvider == nil { 396 var ( 397 accessKey string 398 putPolicy uptoken.PutPolicy 399 ) 400 query := options.BucketQuery 401 if query == nil { 402 bucketHosts := httpclient.DefaultBucketHosts() 403 queryOptions := region.BucketRegionsQueryOptions{ 404 UseInsecureProtocol: options.UseInsecureProtocol, 405 HostFreezeDuration: options.HostFreezeDuration, 406 Client: options.BasicHTTPClient, 407 AccelerateUploading: options.AccelerateUploading, 408 } 409 if hostRetryConfig := options.HostRetryConfig; hostRetryConfig != nil { 410 queryOptions.RetryMax = hostRetryConfig.RetryMax 411 } 412 if query, err = region.NewBucketRegionsQuery(bucketHosts, &queryOptions); err != nil { 413 return 414 } 415 } 416 if accessKey, err = upToken.GetAccessKey(ctx); err != nil { 417 return 418 } 419 if bucketName == "" { 420 if putPolicy, err = upToken.GetPutPolicy(ctx); err != nil { 421 return 422 } else if bucketName, err = putPolicy.GetBucketName(); err != nil { 423 return 424 } 425 } 426 regionsProvider = query.Query(accessKey, bucketName) 427 } 428 regions, err = regionsProvider.GetRegions(ctx) 429 return 430 } 431 432 func forEachRegion(ctx context.Context, objectOptions *ObjectOptions, options *httpclient.Options, fn func(*region.Region) (bool, error)) (err error) { 433 var ( 434 regions []*region.Region 435 retryable bool 436 ) 437 438 if objectOptions.RegionsProvider != nil { 439 regions, err = objectOptions.RegionsProvider.GetRegions(ctx) 440 } else { 441 regions, err = getRegions(ctx, objectOptions.UpToken, objectOptions.BucketName, options) 442 } 443 if err != nil { 444 return 445 } 446 447 if len(regions) == 0 { 448 err = stderrors.New("none of regions got") 449 return 450 } 451 for _, region := range regions { 452 if retryable, err = fn(region); err != nil { 453 if !retryable || !retrier.IsErrorRetryable(err) { 454 break 455 } 456 } else { 457 break 458 } 459 } 460 return 461 } 462 463 type uploadingPartsProgress struct { 464 uploaded uint64 465 uploading map[uint64]uint64 466 lock sync.Mutex 467 } 468 469 func newUploadingPartsProgress() *uploadingPartsProgress { 470 return &uploadingPartsProgress{ 471 uploading: make(map[uint64]uint64), 472 } 473 } 474 475 func (progress *uploadingPartsProgress) setPartUploadingProgress(partNumber, uploaded uint64) { 476 progress.lock.Lock() 477 defer progress.lock.Unlock() 478 479 progress.uploading[partNumber] = uploaded 480 } 481 482 func (progress *uploadingPartsProgress) partUploaded(partNumber, partSize uint64) { 483 progress.lock.Lock() 484 defer progress.lock.Unlock() 485 486 delete(progress.uploading, partNumber) 487 progress.uploaded += partSize 488 } 489 490 func (progress *uploadingPartsProgress) totalUploaded() uint64 { 491 progress.lock.Lock() 492 defer progress.lock.Unlock() 493 494 uploaded := progress.uploaded 495 for _, b := range progress.uploading { 496 uploaded += b 497 } 498 return uploaded 499 }