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 }