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 }