github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/downloader/urls_provider.go (about)

     1  package downloader
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"hash/crc64"
     7  	"net/url"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/qiniu/go-sdk/v7/internal/cache"
    17  	"github.com/qiniu/go-sdk/v7/internal/log"
    18  	"github.com/qiniu/go-sdk/v7/storagev2/apis"
    19  	"github.com/qiniu/go-sdk/v7/storagev2/credentials"
    20  	"github.com/qiniu/go-sdk/v7/storagev2/errors"
    21  	"github.com/qiniu/go-sdk/v7/storagev2/http_client"
    22  	"github.com/qiniu/go-sdk/v7/storagev2/region"
    23  )
    24  
    25  type (
    26  	simpleURLsIter struct {
    27  		urls, used []*url.URL
    28  	}
    29  
    30  	signedURLsIter struct {
    31  		ctx      context.Context
    32  		urlsIter URLsIter
    33  		signer   Signer
    34  		options  *SignOptions
    35  		cache    *cache.Cache
    36  	}
    37  
    38  	staticDomainBasedURLsProvider struct {
    39  		domains []string
    40  	}
    41  
    42  	defaultSrcURLsProvider struct {
    43  		accessKey   string
    44  		query       region.BucketRegionsQuery
    45  		queryOnce   sync.Once
    46  		bucketHosts region.Endpoints
    47  		options     *DefaultSrcURLsProviderOptions
    48  	}
    49  
    50  	domainsQueryURLsProvider struct {
    51  		storage           *apis.Storage
    52  		cache             *cache.Cache
    53  		credentials       credentials.CredentialsProvider
    54  		cacheTTL          time.Duration
    55  		cacheRefreshAfter time.Duration
    56  	}
    57  
    58  	combinedDownloadURLsProviders struct {
    59  		providers []DownloadURLsProvider
    60  	}
    61  
    62  	combinedURLsIter struct {
    63  		iters, used []URLsIter
    64  	}
    65  
    66  	signedDownloadURLsProviders struct {
    67  		provider DownloadURLsProvider
    68  		signer   Signer
    69  		options  *SignOptions
    70  	}
    71  
    72  	// 默认源站域名下载 URL 生成器选项
    73  	DefaultSrcURLsProviderOptions struct {
    74  		region.BucketRegionsQueryOptions
    75  
    76  		// Bucket 服务器地址
    77  		BucketHosts region.Endpoints
    78  	}
    79  
    80  	// 基于域名查询的下载 URL 生成器选项
    81  	DomainsQueryURLsProviderOptions struct {
    82  		http_client.Options
    83  
    84  		// 压缩周期(默认:60s)
    85  		CompactInterval time.Duration
    86  
    87  		// 持久化路径(默认:$TMPDIR/qiniu-golang-sdk/domain_v2_01.cache.json)
    88  		PersistentFilePath string
    89  
    90  		// 持久化周期(默认:60s)
    91  		PersistentDuration time.Duration
    92  
    93  		// 缓存有效周期(默认:3600s)
    94  		CacheTTL time.Duration
    95  
    96  		// 缓存刷新时间(默认:1800s)
    97  		CacheRefreshAfter time.Duration
    98  	}
    99  
   100  	domainCacheValue struct {
   101  		Domains      []string  `json:"domains"`
   102  		RefreshAfter time.Time `json:"refresh_after"`
   103  		ExpiredAt    time.Time `json:"expired_at"`
   104  	}
   105  
   106  	signingCacheValue struct {
   107  		url       *url.URL
   108  		expiredAt time.Time
   109  	}
   110  )
   111  
   112  // 将 URL 列表转换为迭代器
   113  func NewURLsIter(urls []*url.URL) URLsIter {
   114  	return &simpleURLsIter{urls: urls, used: make([]*url.URL, 0, len(urls))}
   115  }
   116  
   117  func (s *simpleURLsIter) Peek(u *url.URL) (bool, error) {
   118  	if len(s.urls) > 0 {
   119  		*u = *s.urls[0]
   120  		return true, nil
   121  	}
   122  	return false, nil
   123  }
   124  
   125  func (s *simpleURLsIter) Next() {
   126  	if len(s.urls) > 0 {
   127  		s.used = append(s.used, s.urls[0])
   128  		s.urls = s.urls[1:]
   129  	}
   130  }
   131  
   132  func (s *simpleURLsIter) Reset() {
   133  	s.urls = append(s.used, s.urls...)
   134  	s.used = make([]*url.URL, 0, cap(s.urls))
   135  }
   136  
   137  func (s *simpleURLsIter) Clone() URLsIter {
   138  	return &simpleURLsIter{
   139  		urls: append(make([]*url.URL, 0, cap(s.urls)), s.urls...),
   140  		used: append(make([]*url.URL, 0, cap(s.used)), s.used...),
   141  	}
   142  }
   143  
   144  // 为 URL 列表签名
   145  func SignURLs(ctx context.Context, urlsIter URLsIter, signer Signer, options *SignOptions) URLsIter {
   146  	return &signedURLsIter{ctx: ctx, urlsIter: urlsIter, signer: signer, options: options, cache: cache.NewCache(1 * time.Second)}
   147  }
   148  
   149  func (s *signedURLsIter) Peek(u *url.URL) (bool, error) {
   150  	var unsignedURL url.URL
   151  	if ok, err := s.urlsIter.Peek(&unsignedURL); err != nil {
   152  		return ok, err
   153  	} else if ok {
   154  		var err error
   155  		cacheValue, status := s.cache.Get(unsignedURL.String(), func() (cache.CacheValue, error) {
   156  			signedURL := unsignedURL
   157  			if err = s.signer.Sign(s.ctx, &signedURL, s.options); err != nil {
   158  				return nil, err
   159  			}
   160  			return signingCacheValue{&signedURL, time.Now().Add(1 * time.Second)}, nil
   161  		})
   162  		if status == cache.NoResultGot {
   163  			return false, err
   164  		}
   165  		*u = *cacheValue.(signingCacheValue).url
   166  		return true, nil
   167  	}
   168  	return false, nil
   169  }
   170  
   171  func (s *signedURLsIter) Next() {
   172  	s.urlsIter.Next()
   173  }
   174  
   175  func (s *signedURLsIter) Reset() {
   176  	s.urlsIter.Reset()
   177  }
   178  
   179  func (s *signedURLsIter) Clone() URLsIter {
   180  	return &signedURLsIter{
   181  		ctx:      s.ctx,
   182  		urlsIter: s.urlsIter.Clone(),
   183  		signer:   s.signer,
   184  		options:  s.options,
   185  		cache:    s.cache,
   186  	}
   187  }
   188  
   189  func (scv signingCacheValue) IsEqual(cv cache.CacheValue) bool {
   190  	return scv.url.String() == cv.(signingCacheValue).url.String()
   191  }
   192  
   193  func (scv signingCacheValue) IsValid() bool {
   194  	return time.Now().Before(scv.expiredAt)
   195  }
   196  
   197  func (scv signingCacheValue) ShouldRefresh() bool {
   198  	return false
   199  }
   200  
   201  // 创建静态域名下载 URL 生成器
   202  func NewStaticDomainBasedURLsProvider(domains []string) DownloadURLsProvider {
   203  	return &staticDomainBasedURLsProvider{domains}
   204  }
   205  
   206  func (g *staticDomainBasedURLsProvider) GetURLsIter(_ context.Context, objectName string, options *GenerateOptions) (URLsIter, error) {
   207  	if options == nil {
   208  		options = &GenerateOptions{}
   209  	}
   210  	urls := make([]*url.URL, 0, len(g.domains))
   211  	for _, domain := range g.domains {
   212  		if !strings.Contains(domain, "://") {
   213  			if options.UseInsecureProtocol {
   214  				domain = "http://" + domain
   215  			} else {
   216  				domain = "https://" + domain
   217  			}
   218  		}
   219  		u, err := url.Parse(domain)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		u.Path = "/" + objectName
   224  		u.RawPath = ""
   225  		u.RawQuery = options.Command
   226  		urls = append(urls, u)
   227  	}
   228  	return NewURLsIter(urls), nil
   229  }
   230  
   231  // 创建默认源站域名下载 URL 生成器
   232  func NewDefaultSrcURLsProvider(accessKey string, options *DefaultSrcURLsProviderOptions) DownloadURLsProvider {
   233  	if options == nil {
   234  		options = &DefaultSrcURLsProviderOptions{}
   235  	}
   236  	bucketHosts := options.BucketHosts
   237  	if bucketHosts.IsEmpty() {
   238  		bucketHosts = http_client.DefaultBucketHosts()
   239  	}
   240  	return &defaultSrcURLsProvider{accessKey: accessKey, bucketHosts: bucketHosts, options: options}
   241  }
   242  
   243  func (g *defaultSrcURLsProvider) GetURLsIter(ctx context.Context, objectName string, options *GenerateOptions) (URLsIter, error) {
   244  	if options == nil {
   245  		options = &GenerateOptions{}
   246  	}
   247  	if options.BucketName == "" {
   248  		return nil, errors.MissingRequiredFieldError{Name: "BucketName"}
   249  	}
   250  
   251  	var err error
   252  	g.queryOnce.Do(func() {
   253  		g.query, err = region.NewBucketRegionsQuery(g.bucketHosts, &g.options.BucketRegionsQueryOptions)
   254  	})
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	accessKey := g.accessKey
   260  	if accessKey == "" {
   261  		if defaultCreds := credentials.Default(); defaultCreds != nil {
   262  			accessKey = defaultCreds.AccessKey
   263  		}
   264  	}
   265  
   266  	regions, err := g.query.Query(accessKey, options.BucketName).GetRegions(ctx)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	if len(regions) == 0 {
   271  		return nil, http_client.ErrNoRegion
   272  	}
   273  	region := regions[0]
   274  	ioSrcDomains := make([]string, 0, len(region.IoSrc.Preferred)+len(region.IoSrc.Alternative))
   275  	ioSrcDomains = append(ioSrcDomains, region.IoSrc.Preferred...)
   276  	ioSrcDomains = append(ioSrcDomains, region.IoSrc.Alternative...)
   277  	return NewStaticDomainBasedURLsProvider(ioSrcDomains).GetURLsIter(ctx, objectName, options)
   278  }
   279  
   280  const cacheFileName = "domain_v2_01.cache.json"
   281  
   282  var (
   283  	persistentCaches     map[uint64]*cache.Cache
   284  	persistentCachesLock sync.Mutex
   285  )
   286  
   287  // 创建基于域名查询的下载 URL 生成器
   288  func NewDomainsQueryURLsProvider(options *DomainsQueryURLsProviderOptions) (DownloadURLsProvider, error) {
   289  	if options == nil {
   290  		options = &DomainsQueryURLsProviderOptions{}
   291  	}
   292  	creds := options.Credentials
   293  	if creds == nil {
   294  		if defaultCreds := credentials.Default(); defaultCreds != nil {
   295  			creds = defaultCreds
   296  		}
   297  	}
   298  	if creds == nil {
   299  		return nil, errors.MissingRequiredFieldError{Name: "Credentials"}
   300  	}
   301  	compactInterval := options.CompactInterval
   302  	if compactInterval == time.Duration(0) {
   303  		compactInterval = time.Minute
   304  	}
   305  	persistentFilePath := options.PersistentFilePath
   306  	if persistentFilePath == "" {
   307  		persistentFilePath = filepath.Join(os.TempDir(), "qiniu-golang-sdk", cacheFileName)
   308  	}
   309  	persistentDuration := options.PersistentDuration
   310  	if persistentDuration == time.Duration(0) {
   311  		persistentDuration = time.Minute
   312  	}
   313  	cacheTTL := options.CacheTTL
   314  	if cacheTTL == time.Duration(0) {
   315  		cacheTTL = time.Hour
   316  	}
   317  	cacheRefreshAfter := options.CacheRefreshAfter
   318  	if cacheRefreshAfter == time.Duration(0) {
   319  		cacheRefreshAfter = time.Hour / 2
   320  	}
   321  	persistentCache, err := getPersistentCache(persistentFilePath, compactInterval, persistentDuration)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	storage := apis.NewStorage(&options.Options)
   327  	return &domainsQueryURLsProvider{storage, persistentCache, creds, cacheTTL, cacheRefreshAfter}, nil
   328  }
   329  
   330  func (g *domainsQueryURLsProvider) GetURLsIter(ctx context.Context, objectName string, options *GenerateOptions) (URLsIter, error) {
   331  	var (
   332  		creds *credentials.Credentials
   333  		err   error
   334  	)
   335  
   336  	if options == nil {
   337  		options = &GenerateOptions{}
   338  	}
   339  	if options.BucketName == "" {
   340  		return nil, errors.MissingRequiredFieldError{Name: "BucketName"}
   341  	}
   342  	if creds, err = g.credentials.Get(ctx); err != nil {
   343  		return nil, err
   344  	}
   345  	cacheKey := fmt.Sprintf("%s:%s", creds.AccessKey, options.BucketName)
   346  
   347  	cacheValue, status := g.cache.Get(cacheKey, func() (cache.CacheValue, error) {
   348  		response, err := g.storage.GetBucketDomains(ctx, &apis.GetBucketDomainsRequest{BucketName: options.BucketName}, nil)
   349  		if err != nil {
   350  			return nil, err
   351  		} else {
   352  			now := time.Now()
   353  			return &domainCacheValue{
   354  				Domains:      response.Domains,
   355  				RefreshAfter: now.Add(g.cacheRefreshAfter),
   356  				ExpiredAt:    now.Add(g.cacheTTL),
   357  			}, nil
   358  		}
   359  	})
   360  	if status == cache.NoResultGot {
   361  		return nil, err
   362  	}
   363  	domains := cacheValue.(*domainCacheValue).Domains
   364  	return NewStaticDomainBasedURLsProvider(domains).GetURLsIter(ctx, objectName, options)
   365  }
   366  
   367  func (left *domainCacheValue) IsEqual(rightValue cache.CacheValue) bool {
   368  	if right, ok := rightValue.(*domainCacheValue); ok {
   369  		if len(left.Domains) != len(right.Domains) {
   370  			return false
   371  		}
   372  		for idx := range left.Domains {
   373  			if left.Domains[idx] != right.Domains[idx] {
   374  				return false
   375  			}
   376  		}
   377  		return true
   378  	}
   379  	return false
   380  }
   381  
   382  func (left *domainCacheValue) IsValid() bool {
   383  	return time.Now().Before(left.ExpiredAt)
   384  }
   385  
   386  func (left *domainCacheValue) ShouldRefresh() bool {
   387  	return time.Now().After(left.RefreshAfter)
   388  }
   389  
   390  func getPersistentCache(persistentFilePath string, compactInterval, persistentDuration time.Duration) (*cache.Cache, error) {
   391  	var (
   392  		persistentCache *cache.Cache
   393  		ok              bool
   394  		err             error
   395  	)
   396  
   397  	crc64Value := calcPersistentCacheCrc64(persistentFilePath, compactInterval, persistentDuration)
   398  	persistentCachesLock.Lock()
   399  	defer persistentCachesLock.Unlock()
   400  
   401  	if persistentCaches == nil {
   402  		persistentCaches = make(map[uint64]*cache.Cache)
   403  	}
   404  	if persistentCache, ok = persistentCaches[crc64Value]; !ok {
   405  		persistentCache, err = cache.NewPersistentCache(
   406  			reflect.TypeOf(&domainCacheValue{}),
   407  			persistentFilePath,
   408  			compactInterval,
   409  			persistentDuration,
   410  			func(err error) {
   411  				log.Warn(fmt.Sprintf("DomainsURLsProvider persist error: %s", err))
   412  			})
   413  		if err != nil {
   414  			return nil, err
   415  		}
   416  		persistentCaches[crc64Value] = persistentCache
   417  	}
   418  	return persistentCache, nil
   419  }
   420  
   421  func calcPersistentCacheCrc64(persistentFilePath string, compactInterval, persistentDuration time.Duration) uint64 {
   422  	bytes := make([]byte, 0, 1024)
   423  	bytes = strconv.AppendInt(bytes, int64(compactInterval), 36)
   424  	bytes = append(bytes, []byte(persistentFilePath)...)
   425  	bytes = append(bytes, byte(0))
   426  	bytes = strconv.AppendInt(bytes, int64(persistentDuration), 36)
   427  	return crc64.Checksum(bytes, crc64.MakeTable(crc64.ISO))
   428  }
   429  
   430  // 合并多个下载 URL 生成器
   431  func CombineDownloadURLsProviders(providers ...DownloadURLsProvider) DownloadURLsProvider {
   432  	return combinedDownloadURLsProviders{providers}
   433  }
   434  
   435  func (g combinedDownloadURLsProviders) GetURLsIter(ctx context.Context, objectName string, options *GenerateOptions) (URLsIter, error) {
   436  	urlIters := make([]URLsIter, 0, len(g.providers))
   437  	for _, downloadURLsProvider := range g.providers {
   438  		urlsIter, err := downloadURLsProvider.GetURLsIter(ctx, objectName, options)
   439  		if err != nil {
   440  			return nil, err
   441  		}
   442  		urlIters = append(urlIters, urlsIter)
   443  	}
   444  	return &combinedURLsIter{iters: urlIters, used: make([]URLsIter, 0, len(urlIters))}, nil
   445  }
   446  
   447  func (c *combinedURLsIter) Peek(u *url.URL) (bool, error) {
   448  	for len(c.iters) > 0 {
   449  		iter := c.iters[0]
   450  		if ok, err := iter.Peek(u); err != nil {
   451  			return ok, err
   452  		} else if ok {
   453  			return true, nil
   454  		} else {
   455  			c.used = append(c.used, c.iters[0])
   456  			c.iters = c.iters[1:]
   457  		}
   458  	}
   459  	return false, nil
   460  }
   461  
   462  func (c *combinedURLsIter) Next() {
   463  	if len(c.iters) > 0 {
   464  		iter := c.iters[0]
   465  		iter.Next()
   466  	}
   467  }
   468  
   469  func (c *combinedURLsIter) Reset() {
   470  	c.iters = append(c.used, c.iters...)
   471  	c.used = make([]URLsIter, 0, cap(c.iters))
   472  }
   473  
   474  func (c *combinedURLsIter) Clone() URLsIter {
   475  	new := combinedURLsIter{
   476  		iters: make([]URLsIter, 0, cap(c.iters)),
   477  		used:  make([]URLsIter, 0, cap(c.used)),
   478  	}
   479  	for _, iter := range c.iters {
   480  		new.iters = append(new.iters, iter.Clone())
   481  	}
   482  	for _, iter := range c.used {
   483  		new.used = append(new.used, iter.Clone())
   484  	}
   485  	return &new
   486  }
   487  
   488  // 为下载 URL 获取结果签名
   489  func SignURLsProvider(provider DownloadURLsProvider, signer Signer, options *SignOptions) DownloadURLsProvider {
   490  	return signedDownloadURLsProviders{provider, signer, options}
   491  }
   492  
   493  func (provider signedDownloadURLsProviders) GetURLsIter(ctx context.Context, objectName string, options *GenerateOptions) (URLsIter, error) {
   494  	if urlsIter, err := provider.provider.GetURLsIter(ctx, objectName, options); err == nil {
   495  		return SignURLs(ctx, urlsIter, provider.signer, provider.options), nil
   496  	} else {
   497  		return urlsIter, err
   498  	}
   499  }