github.com/qiniu/go-sdk/v7@v7.27.0/storage/region_uc_v2.go (about)

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  	"time"
    14  
    15  	"golang.org/x/sync/singleflight"
    16  
    17  	"github.com/qiniu/go-sdk/v7/client"
    18  	"github.com/qiniu/go-sdk/v7/internal/clientv2"
    19  	"github.com/qiniu/go-sdk/v7/storagev2/apis"
    20  	"github.com/qiniu/go-sdk/v7/storagev2/backoff"
    21  	"github.com/qiniu/go-sdk/v7/storagev2/chooser"
    22  	"github.com/qiniu/go-sdk/v7/storagev2/http_client"
    23  	"github.com/qiniu/go-sdk/v7/storagev2/resolver"
    24  	"github.com/qiniu/go-sdk/v7/storagev2/retrier"
    25  )
    26  
    27  // 此处废弃,但为了兼容老版本,单独放置一个文件
    28  
    29  // UcQueryRet 为查询请求的回复
    30  type UcQueryRet struct {
    31  	TTL       int                            `json:"ttl"`
    32  	Io        map[string]map[string][]string `json:"-"`
    33  	IoInfo    map[string]UcQueryIo           `json:"io"`
    34  	IoSrcInfo map[string]UcQueryIo           `json:"io_src"`
    35  	Up        map[string]UcQueryUp           `json:"up"`
    36  	RsInfo    map[string]UcQueryServerInfo   `json:"rs"`
    37  	RsfInfo   map[string]UcQueryServerInfo   `json:"rsf"`
    38  	ApiInfo   map[string]UcQueryServerInfo   `json:"api"`
    39  }
    40  
    41  type tmpUcQueryRet UcQueryRet
    42  
    43  func (uc *UcQueryRet) UnmarshalJSON(data []byte) error {
    44  	var tmp tmpUcQueryRet
    45  	if err := json.Unmarshal(data, &tmp); err != nil {
    46  		return err
    47  	}
    48  
    49  	uc.TTL = tmp.TTL
    50  	uc.IoInfo = tmp.IoInfo
    51  	uc.IoSrcInfo = tmp.IoSrcInfo
    52  	uc.Up = tmp.Up
    53  	uc.RsInfo = tmp.RsInfo
    54  	uc.RsfInfo = tmp.RsfInfo
    55  	uc.ApiInfo = tmp.ApiInfo
    56  	uc.setup()
    57  	return nil
    58  }
    59  
    60  func (uc *UcQueryRet) setup() {
    61  	if uc.Io != nil || uc.IoInfo == nil {
    62  		return
    63  	}
    64  
    65  	uc.Io = make(map[string]map[string][]string)
    66  	ioSrc := uc.IoInfo["src"].toMapWithoutInfo()
    67  	if len(ioSrc) > 0 {
    68  		uc.Io["src"] = ioSrc
    69  	}
    70  
    71  	ioOldSrc := uc.IoInfo["old_src"].toMapWithoutInfo()
    72  	if len(ioOldSrc) > 0 {
    73  		uc.Io["old_src"] = ioOldSrc
    74  	}
    75  }
    76  
    77  type (
    78  	UcQueryUp = UcQueryServerInfo
    79  	UcQueryIo = UcQueryServerInfo
    80  )
    81  
    82  // UcQueryServerInfo 为查询请求回复中的上传域名信息
    83  type UcQueryServerInfo struct {
    84  	Main   []string `json:"main,omitempty"`
    85  	Backup []string `json:"backup,omitempty"`
    86  	Info   string   `json:"info,omitempty"`
    87  }
    88  
    89  func (io UcQueryServerInfo) toMapWithoutInfo() map[string][]string {
    90  	ret := make(map[string][]string)
    91  	if len(io.Main) > 0 {
    92  		ret["main"] = io.Main
    93  	}
    94  
    95  	if len(io.Backup) > 0 {
    96  		ret["backup"] = io.Backup
    97  	}
    98  
    99  	return ret
   100  }
   101  
   102  var ucQueryV2Group singleflight.Group
   103  
   104  type regionV2CacheValue struct {
   105  	Region   *Region   `json:"region"`
   106  	Deadline time.Time `json:"deadline"`
   107  }
   108  
   109  type regionV2CacheMap map[string]regionV2CacheValue
   110  
   111  const regionV2CacheFileName = "query_v2_00.cache.json"
   112  
   113  var (
   114  	regionV2CachePath     = filepath.Join(os.TempDir(), "qiniu-golang-sdk", regionV2CacheFileName)
   115  	regionV2Cache         sync.Map
   116  	regionV2CacheLock     sync.RWMutex
   117  	regionV2CacheSyncLock sync.Mutex
   118  	regionV2CacheLoaded   bool = false
   119  )
   120  
   121  func setRegionV2CachePath(newPath string) {
   122  	cacheDir := filepath.Dir(newPath)
   123  	if len(cacheDir) == 0 {
   124  		return
   125  	}
   126  
   127  	regionV2CacheLock.Lock()
   128  	defer regionV2CacheLock.Unlock()
   129  
   130  	regionV2CachePath = filepath.Join(cacheDir, regionV2CacheFileName)
   131  	regionV2CacheLoaded = false
   132  }
   133  
   134  func loadRegionV2Cache() {
   135  	cacheFile, err := os.Open(regionV2CachePath)
   136  	if err != nil {
   137  		return
   138  	}
   139  	defer cacheFile.Close()
   140  
   141  	var cacheMap regionV2CacheMap
   142  	if err = json.NewDecoder(cacheFile).Decode(&cacheMap); err != nil {
   143  		return
   144  	}
   145  	for cacheKey, cacheValue := range cacheMap {
   146  		regionV2Cache.Store(cacheKey, cacheValue)
   147  	}
   148  }
   149  
   150  func storeRegionV2Cache() {
   151  	err := os.MkdirAll(filepath.Dir(regionV2CachePath), 0700)
   152  	if err != nil {
   153  		return
   154  	}
   155  
   156  	cacheFile, err := os.OpenFile(regionV2CachePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
   157  	if err != nil {
   158  		return
   159  	}
   160  	defer cacheFile.Close()
   161  
   162  	cacheMap := make(regionV2CacheMap)
   163  	regionV2Cache.Range(func(cacheKey, cacheValue interface{}) bool {
   164  		cacheMap[cacheKey.(string)] = cacheValue.(regionV2CacheValue)
   165  		return true
   166  	})
   167  	if err = json.NewEncoder(cacheFile).Encode(cacheMap); err != nil {
   168  		return
   169  	}
   170  }
   171  
   172  type UCApiOptions struct {
   173  	// 是否使用 HTTPS 协议
   174  	UseHttps bool
   175  
   176  	// 是否加速上传
   177  	AccelerateUploading bool
   178  
   179  	// 单域名重试次数
   180  	RetryMax int
   181  
   182  	// api 请求的域名
   183  	Hosts []string
   184  
   185  	// 主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 TryTimes 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。
   186  	HostFreezeDuration time.Duration
   187  
   188  	// api 请求使用的 client
   189  	Client *client.Client
   190  
   191  	// api 使用的域名解析器
   192  	Resolver resolver.Resolver
   193  
   194  	// api 使用的 IP 选择器
   195  	Chooser chooser.Chooser
   196  
   197  	// api 使用的退避器
   198  	Backoff backoff.Backoff
   199  
   200  	// api 使用的重试器
   201  	Retrier retrier.Retrier
   202  
   203  	// 签名前回调函数
   204  	BeforeSign func(*http.Request)
   205  
   206  	// 签名后回调函数
   207  	AfterSign func(*http.Request)
   208  
   209  	// 签名失败回调函数
   210  	SignError func(*http.Request, error)
   211  
   212  	// 域名解析前回调函数
   213  	BeforeResolve func(*http.Request)
   214  
   215  	// 域名解析后回调函数
   216  	AfterResolve func(*http.Request, []net.IP)
   217  
   218  	// 域名解析错误回调函数
   219  	ResolveError func(*http.Request, error)
   220  
   221  	// 退避前回调函数
   222  	BeforeBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration)
   223  
   224  	// 退避后回调函数
   225  	AfterBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration)
   226  
   227  	// 请求前回调函数
   228  	BeforeRequest func(*http.Request, *retrier.RetrierOptions)
   229  
   230  	// 请求后回调函数
   231  	AfterResponse func(*http.Response, *retrier.RetrierOptions, error)
   232  }
   233  
   234  func (options *UCApiOptions) getApiStorageClient() *apis.Storage {
   235  	return apis.NewStorage(&http_client.Options{
   236  		Interceptors:        []clientv2.Interceptor{},
   237  		UseInsecureProtocol: !options.UseHttps,
   238  		AccelerateUploading: options.AccelerateUploading,
   239  		Resolver:            options.Resolver,
   240  		Chooser:             options.Chooser,
   241  		HostRetryConfig:     &clientv2.RetryConfig{RetryMax: options.RetryMax, Retrier: options.Retrier},
   242  		HostsRetryConfig:    &clientv2.RetryConfig{Retrier: options.Retrier},
   243  		HostFreezeDuration:  options.HostFreezeDuration,
   244  		BeforeSign:          options.BeforeSign,
   245  		AfterSign:           options.AfterSign,
   246  		SignError:           options.SignError,
   247  		BeforeResolve:       options.BeforeResolve,
   248  		AfterResolve:        options.AfterResolve,
   249  		ResolveError:        options.ResolveError,
   250  		BeforeBackoff:       options.BeforeBackoff,
   251  		AfterBackoff:        options.AfterBackoff,
   252  		BeforeRequest:       options.BeforeRequest,
   253  		AfterResponse:       options.AfterResponse,
   254  	})
   255  }
   256  
   257  func (options *UCApiOptions) getApiOptions() *apis.Options {
   258  	return &apis.Options{OverwrittenBucketHosts: getUcEndpointProvider(options.UseHttps, options.Hosts)}
   259  }
   260  
   261  func DefaultUCApiOptions() UCApiOptions {
   262  	return UCApiOptions{
   263  		UseHttps: true,
   264  	}
   265  }
   266  
   267  func getRegionByV2(ak, bucket string, options UCApiOptions) (*Region, error) {
   268  	regionV2CacheLock.RLock()
   269  	if regionV2CacheLoaded {
   270  		regionV2CacheLock.RUnlock()
   271  	} else {
   272  		regionV2CacheLock.RUnlock()
   273  		func() {
   274  			regionV2CacheLock.Lock()
   275  			defer regionV2CacheLock.Unlock()
   276  
   277  			if !regionV2CacheLoaded {
   278  				loadRegionV2Cache()
   279  				regionV2CacheLoaded = true
   280  			}
   281  		}()
   282  	}
   283  
   284  	regionCacheKey := makeRegionCacheKey(ak, bucket, options.Hosts, options.AccelerateUploading)
   285  	// check from cache
   286  	if v, ok := regionV2Cache.Load(regionCacheKey); ok && time.Now().Before(v.(regionV2CacheValue).Deadline) {
   287  		return v.(regionV2CacheValue).Region, nil
   288  	}
   289  
   290  	newRegion, err, _ := ucQueryV2Group.Do(regionCacheKey, func() (interface{}, error) {
   291  		region, ttl, err := _getRegionByV2WithoutCache(ak, bucket, options)
   292  		if err != nil {
   293  			return nil, fmt.Errorf("query region error, %s", err.Error())
   294  		}
   295  
   296  		regionV2Cache.Store(regionCacheKey, regionV2CacheValue{
   297  			Region:   region,
   298  			Deadline: time.Now().Add(time.Duration(ttl) * time.Second),
   299  		})
   300  
   301  		regionV2CacheSyncLock.Lock()
   302  		defer regionV2CacheSyncLock.Unlock()
   303  
   304  		storeRegionV2Cache()
   305  		return region, nil
   306  	})
   307  	if newRegion == nil {
   308  		return nil, err
   309  	}
   310  
   311  	return newRegion.(*Region), err
   312  }
   313  
   314  func makeRegionCacheKey(ak, bucket string, ucHosts []string, accelerateUploading bool) string {
   315  	hostStrings := fmt.Sprintf("%v", ucHosts)
   316  	s := fmt.Sprintf("%s:%s:%x", ak, bucket, md5.Sum([]byte(hostStrings)))
   317  	if accelerateUploading {
   318  		s += ":1"
   319  	}
   320  	return s
   321  }
   322  
   323  func _getRegionByV2WithoutCache(ak, bucket string, options UCApiOptions) (*Region, int64, error) {
   324  	response, err := options.getApiStorageClient().QueryBucketV2(
   325  		context.Background(),
   326  		&apis.QueryBucketV2Request{
   327  			Bucket:    bucket,
   328  			AccessKey: ak,
   329  		},
   330  		options.getApiOptions(),
   331  	)
   332  	if err != nil {
   333  		return nil, 0, err
   334  	}
   335  	var srcUpHosts, cdnUpHosts []string
   336  	var rsHost, rsfHost, apiHost, ioVipHost, ioSrcHost string
   337  	if options.AccelerateUploading && len(response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains) > 0 {
   338  		srcUpHosts = make([]string, 0,
   339  			len(response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains)+
   340  				len(response.UpDomains.AcceleratedUpDomains.BackupAcceleratedUpDomains)+
   341  				len(response.UpDomains.OldAcceleratedDomains.OldMainAcceleratedUpDomains)+
   342  				len(response.UpDomains.SourceUpDomains.MainSourceUpDomains)+
   343  				len(response.UpDomains.SourceUpDomains.BackupSourceUpDomains)+
   344  				len(response.UpDomains.OldSourceDomains.OldMainSourceUpDomains))
   345  		srcUpHosts = append(srcUpHosts, response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains...)
   346  		srcUpHosts = append(srcUpHosts, response.UpDomains.AcceleratedUpDomains.BackupAcceleratedUpDomains...)
   347  		srcUpHosts = append(srcUpHosts, response.UpDomains.OldAcceleratedDomains.OldMainAcceleratedUpDomains...)
   348  		srcUpHosts = append(srcUpHosts, response.UpDomains.SourceUpDomains.MainSourceUpDomains...)
   349  		srcUpHosts = append(srcUpHosts, response.UpDomains.SourceUpDomains.BackupSourceUpDomains...)
   350  		srcUpHosts = append(srcUpHosts, response.UpDomains.OldSourceDomains.OldMainSourceUpDomains...)
   351  		cdnUpHosts = make([]string, 0, len(response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains))
   352  		cdnUpHosts = append(cdnUpHosts, response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains...)
   353  	} else {
   354  		srcUpHosts = make([]string, 0,
   355  			len(response.UpDomains.SourceUpDomains.MainSourceUpDomains)+
   356  				len(response.UpDomains.SourceUpDomains.BackupSourceUpDomains)+
   357  				len(response.UpDomains.OldSourceDomains.OldMainSourceUpDomains))
   358  		srcUpHosts = append(srcUpHosts, response.UpDomains.SourceUpDomains.MainSourceUpDomains...)
   359  		srcUpHosts = append(srcUpHosts, response.UpDomains.SourceUpDomains.BackupSourceUpDomains...)
   360  		srcUpHosts = append(srcUpHosts, response.UpDomains.OldSourceDomains.OldMainSourceUpDomains...)
   361  		cdnUpHosts = make([]string, 0,
   362  			len(response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains)+
   363  				len(response.UpDomains.AcceleratedUpDomains.BackupAcceleratedUpDomains)+
   364  				len(response.UpDomains.OldAcceleratedDomains.OldMainAcceleratedUpDomains))
   365  		cdnUpHosts = append(cdnUpHosts, response.UpDomains.AcceleratedUpDomains.MainAcceleratedUpDomains...)
   366  		cdnUpHosts = append(cdnUpHosts, response.UpDomains.AcceleratedUpDomains.BackupAcceleratedUpDomains...)
   367  		cdnUpHosts = append(cdnUpHosts, response.UpDomains.OldAcceleratedDomains.OldMainAcceleratedUpDomains...)
   368  	}
   369  	if len(response.RsDomains.AcceleratedRsDomains.MainAcceleratedRsDomains) > 0 {
   370  		rsHost = response.RsDomains.AcceleratedRsDomains.MainAcceleratedRsDomains[0]
   371  	}
   372  	if len(response.RsfDomains.AcceleratedRsfDomains.MainAcceleratedRsfDomains) > 0 {
   373  		rsfHost = response.RsfDomains.AcceleratedRsfDomains.MainAcceleratedRsfDomains[0]
   374  	}
   375  	if len(response.ApiDomains.AcceleratedApiDomains.MainAcceleratedApiDomains) > 0 {
   376  		apiHost = response.ApiDomains.AcceleratedApiDomains.MainAcceleratedApiDomains[0]
   377  	}
   378  	if len(response.IoDomains.SourceIoDomains.MainSourceIoDomains) > 0 {
   379  		ioVipHost = response.IoDomains.SourceIoDomains.MainSourceIoDomains[0]
   380  	}
   381  	if len(response.IoSrcDomains.SourceIoSrcDomains.MainSourceIoSrcDomains) > 0 {
   382  		ioSrcHost = response.IoSrcDomains.SourceIoSrcDomains.MainSourceIoSrcDomains[0]
   383  	}
   384  	return &Region{
   385  		SrcUpHosts: srcUpHosts,
   386  		CdnUpHosts: cdnUpHosts,
   387  		RsHost:     rsHost,
   388  		RsfHost:    rsfHost,
   389  		ApiHost:    apiHost,
   390  		IovipHost:  ioVipHost,
   391  		IoSrcHost:  ioSrcHost,
   392  	}, response.TimeToLive, nil
   393  }