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  }