github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/region/all.go (about)

     1  package region
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/qiniu/go-sdk/v7/internal/cache"
    14  	"github.com/qiniu/go-sdk/v7/internal/clientv2"
    15  	"github.com/qiniu/go-sdk/v7/storagev2/backoff"
    16  	"github.com/qiniu/go-sdk/v7/storagev2/chooser"
    17  	"github.com/qiniu/go-sdk/v7/storagev2/credentials"
    18  	"github.com/qiniu/go-sdk/v7/storagev2/resolver"
    19  	"github.com/qiniu/go-sdk/v7/storagev2/retrier"
    20  )
    21  
    22  type (
    23  	allRegionsProvider struct {
    24  		credentials credentials.CredentialsProvider
    25  		bucketHosts Endpoints
    26  		cache       *cache.Cache
    27  		client      clientv2.Client
    28  		useHttps    bool
    29  	}
    30  
    31  	// AllRegionsProviderOptions 所有区域提供者选项
    32  	AllRegionsProviderOptions struct {
    33  		// 使用 HTTP 协议
    34  		UseInsecureProtocol bool
    35  
    36  		// 压缩周期(默认:60s)
    37  		CompactInterval time.Duration
    38  
    39  		// 持久化路径(默认:$TMPDIR/qiniu-golang-sdk/regions_v4_01.cache.json)
    40  		PersistentFilePath string
    41  
    42  		// 持久化周期(默认:60s)
    43  		PersistentDuration time.Duration
    44  
    45  		// 单域名重试次数(默认:2)
    46  		RetryMax int
    47  
    48  		// 主备域名冻结时间(默认:600s),当一个域名请求失败(单个域名会被重试 RetryMax 次),会被冻结一段时间,使用备用域名进行重试,在冻结时间内,域名不能被使用,当一个操作中所有域名竣备冻结操作不在进行重试,返回最后一次操作的错误。
    49  		HostFreezeDuration time.Duration
    50  
    51  		// HTTP 客户端,如果不配置则使用默认的 HTTP 客户端
    52  		Client clientv2.Client
    53  
    54  		// 域名解析器,如果不配置则使用默认的域名解析器
    55  		Resolver resolver.Resolver
    56  
    57  		// 域名选择器,如果不配置则使用默认的域名选择器
    58  		Chooser chooser.Chooser
    59  
    60  		// 退避器,如果不配置则使用默认的退避器
    61  		Backoff backoff.Backoff
    62  
    63  		// 签名前回调函数
    64  		BeforeSign func(*http.Request)
    65  
    66  		// 签名后回调函数
    67  		AfterSign func(*http.Request)
    68  
    69  		// 签名错误回调函数
    70  		SignError func(*http.Request, error)
    71  
    72  		// 域名解析前回调函数
    73  		BeforeResolve func(*http.Request)
    74  
    75  		// 域名解析后回调函数
    76  		AfterResolve func(*http.Request, []net.IP)
    77  
    78  		// 域名解析错误回调函数
    79  		ResolveError func(*http.Request, error)
    80  
    81  		// 退避前回调函数
    82  		BeforeBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration)
    83  
    84  		// 退避后回调函数
    85  		AfterBackoff func(*http.Request, *retrier.RetrierOptions, time.Duration)
    86  
    87  		// 请求前回调函数
    88  		BeforeRequest func(*http.Request, *retrier.RetrierOptions)
    89  
    90  		// 请求后回调函数
    91  		AfterResponse func(*http.Response, *retrier.RetrierOptions, error)
    92  	}
    93  
    94  	singleRegion struct {
    95  		ID  string              `json:"id"`
    96  		Ttl int64               `json:"ttl"`
    97  		Io  v4QueryServiceHosts `json:"io"`
    98  		Up  v4QueryServiceHosts `json:"up"`
    99  		Rs  v4QueryServiceHosts `json:"rs"`
   100  		Rsf v4QueryServiceHosts `json:"rsf"`
   101  		Api v4QueryServiceHosts `json:"api"`
   102  		Uc  v4QueryServiceHosts `json:"uc"`
   103  	}
   104  
   105  	regionsResponse struct {
   106  		Regions []singleRegion `json:"regions"`
   107  	}
   108  )
   109  
   110  const allRegionsProviderCacheFileName = "regions_v4_01.cache.json"
   111  
   112  // NewAllRegionsProvider 创建所有空间提供者
   113  func NewAllRegionsProvider(credentials credentials.CredentialsProvider, bucketHosts Endpoints, opts *AllRegionsProviderOptions) (RegionsProvider, error) {
   114  	if opts == nil {
   115  		opts = &AllRegionsProviderOptions{}
   116  	}
   117  	retryMax := opts.RetryMax
   118  	if retryMax <= 0 {
   119  		retryMax = 2
   120  	}
   121  	compactInterval := opts.CompactInterval
   122  	if compactInterval == time.Duration(0) {
   123  		compactInterval = time.Minute
   124  	}
   125  	persistentFilePath := opts.PersistentFilePath
   126  	if persistentFilePath == "" {
   127  		persistentFilePath = filepath.Join(os.TempDir(), "qiniu-golang-sdk", allRegionsProviderCacheFileName)
   128  	}
   129  	persistentDuration := opts.PersistentDuration
   130  	if persistentDuration == time.Duration(0) {
   131  		persistentDuration = time.Minute
   132  	}
   133  
   134  	persistentCache, err := getPersistentCache(persistentFilePath, compactInterval, persistentDuration)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return &allRegionsProvider{
   140  		credentials: credentials,
   141  		bucketHosts: bucketHosts,
   142  		cache:       persistentCache,
   143  		client: makeBucketQueryClient(
   144  			opts.Client,
   145  			credentials,
   146  			bucketHosts,
   147  			!opts.UseInsecureProtocol,
   148  			retryMax,
   149  			opts.HostFreezeDuration,
   150  			opts.Resolver,
   151  			opts.Chooser,
   152  			opts.Backoff,
   153  			opts.BeforeResolve,
   154  			opts.AfterResolve,
   155  			opts.ResolveError,
   156  			opts.BeforeBackoff,
   157  			opts.AfterBackoff,
   158  			opts.BeforeRequest,
   159  			opts.AfterResponse,
   160  			opts.BeforeSign,
   161  			opts.AfterSign,
   162  			opts.SignError,
   163  		),
   164  		useHttps: !opts.UseInsecureProtocol,
   165  	}, nil
   166  }
   167  
   168  func (provider *allRegionsProvider) GetRegions(ctx context.Context) ([]*Region, error) {
   169  	creds, err := provider.credentials.Get(ctx)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	cacheValue, status := provider.cache.Get(makeRegionsProviderCacheKey(creds.AccessKey, provider.bucketHosts), func() (cache.CacheValue, error) {
   174  		var ret regionsResponse
   175  		url := provider.bucketHosts.firstUrl(provider.useHttps) + "/regions"
   176  		if err = clientv2.DoAndDecodeJsonResponse(provider.client, clientv2.RequestParams{
   177  			Context:        ctx,
   178  			Method:         clientv2.RequestMethodGet,
   179  			Url:            url,
   180  			BufferResponse: true,
   181  		}, &ret); err != nil {
   182  			return nil, err
   183  		}
   184  		return ret.toCacheValue(), nil
   185  	})
   186  	if status == cache.NoResultGot {
   187  		return nil, err
   188  	}
   189  	return cacheValue.(*v4QueryCacheValue).Regions, nil
   190  }
   191  
   192  func (response *regionsResponse) toCacheValue() *v4QueryCacheValue {
   193  	var (
   194  		minTtl  = int64(math.MaxInt64)
   195  		regions = make([]*Region, 0, len(response.Regions))
   196  	)
   197  	for _, host := range response.Regions {
   198  		regions = append(regions, host.toCacheValue())
   199  		if host.Ttl < minTtl {
   200  			minTtl = host.Ttl
   201  		}
   202  	}
   203  	now := time.Now()
   204  	return &v4QueryCacheValue{
   205  		Regions:      regions,
   206  		RefreshAfter: now.Add(time.Duration(minTtl/2) * time.Second),
   207  		ExpiredAt:    now.Add(time.Duration(minTtl) * time.Second),
   208  	}
   209  }
   210  
   211  func (response *singleRegion) toCacheValue() *Region {
   212  	return &Region{
   213  		RegionID: response.ID,
   214  		Up:       response.Up.toCacheValue(),
   215  		Io:       response.Io.toCacheValue(),
   216  		Rs:       response.Rs.toCacheValue(),
   217  		Rsf:      response.Rsf.toCacheValue(),
   218  		Api:      response.Api.toCacheValue(),
   219  		Bucket:   response.Uc.toCacheValue(),
   220  	}
   221  }
   222  
   223  func makeRegionsProviderCacheKey(accessKey string, bucketHosts Endpoints) string {
   224  	return fmt.Sprintf("%s:%s", accessKey, makeBucketHostsCacheKey(bucketHosts))
   225  }