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

     1  package region
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/qiniu/go-sdk/v7/internal/freezer"
    12  	"github.com/qiniu/go-sdk/v7/internal/hostprovider"
    13  )
    14  
    15  type (
    16  	// 服务地址
    17  	//
    18  	// 可以存储域名或 IP,端口和协议可选
    19  	Endpoints struct {
    20  		Preferred   []string `json:"preferred,omitempty"`   // 首选服务地址
    21  		Alternative []string `json:"alternative,omitempty"` // 备选服务地址
    22  		Accelerated []string `json:"accelerated,omitempty"` // 加速服务地址
    23  	}
    24  
    25  	// 区域信息
    26  	//
    27  	// 可能有多个机房信息,每个机房可能有多个服务地址
    28  	//
    29  	// 如果使用公有云,建议使用 GetRegionByID 方法直接获取获取区域实例,不建议手动设置服务地址
    30  	Region struct {
    31  		RegionID string    `json:"region_id,omitempty"` // 区域 ID
    32  		Up       Endpoints `json:"up,omitempty"`        // Up 服务域名
    33  		Io       Endpoints `json:"io,omitempty"`        // Io 服务域名
    34  		IoSrc    Endpoints `json:"io_src,omitempty"`    // Io 源站服务域名
    35  		Rs       Endpoints `json:"rs,omitempty"`        // Rs 服务域名
    36  		Rsf      Endpoints `json:"rsf,omitempty"`       // Rsf 服务域名
    37  		Api      Endpoints `json:"api,omitempty"`       // Api 服务域名
    38  		Bucket   Endpoints `json:"bucket,omitempty"`    // Bucket 服务域名
    39  	}
    40  
    41  	// 区域提供者
    42  	RegionsProvider interface {
    43  		GetRegions(context.Context) ([]*Region, error)
    44  	}
    45  
    46  	// 服务名称
    47  	ServiceName string
    48  
    49  	endpointsStatus uint8
    50  
    51  	// 服务地址迭代器
    52  	EndpointsIter struct {
    53  		endpoints Endpoints
    54  		index     int
    55  		current   endpointsStatus
    56  	}
    57  
    58  	// 服务地址提供者
    59  	EndpointsProvider interface {
    60  		GetEndpoints(context.Context) (Endpoints, error)
    61  	}
    62  
    63  	endpointsHostProvider struct {
    64  		iter          *EndpointsIter
    65  		freezer       freezer.Freezer
    66  		lastFreezeErr error
    67  	}
    68  )
    69  
    70  const (
    71  	endpointsStatusAccelerated endpointsStatus = iota
    72  	endpointsStatusPreferred
    73  	endpointsStatusAlternative
    74  )
    75  
    76  const (
    77  	// Up 服务
    78  	ServiceUp ServiceName = "up"
    79  	// Io 服务
    80  	ServiceIo ServiceName = "io"
    81  	// Io 源站服务
    82  	ServiceIoSrc ServiceName = "io_src"
    83  	// Rs 服务
    84  	ServiceRs ServiceName = "rs"
    85  	// Rsf 服务
    86  	ServiceRsf ServiceName = "rsf"
    87  	// Api 服务
    88  	ServiceApi ServiceName = "api"
    89  	// Bucket 服务
    90  	ServiceBucket ServiceName = "bucket"
    91  )
    92  
    93  var ErrUnrecognizedServiceName = errors.New("unrecognized service name")
    94  
    95  // 根据 RegionID 获取公有云区域信息
    96  func GetRegionByID(regionID string, useHttps bool) *Region {
    97  	region := &Region{RegionID: regionID}
    98  	if regionID == "z0" {
    99  		region.Up.Preferred = []string{makeHost("upload.qiniup.com", useHttps), makeHost("upload-z0.qiniup.com", useHttps), makeHost("up.qiniup.com", useHttps), makeHost("up-z0.qiniup.com", useHttps)}
   100  		region.Up.Alternative = []string{makeHost("up.qbox.me", useHttps), makeHost("up-z0.qbox.me", useHttps)}
   101  		region.Io.Preferred = []string{makeHost("iovip.qiniuio.com", useHttps), makeHost("iovip-z0.qiniuio.com", useHttps)}
   102  		region.Io.Alternative = []string{makeHost("iovip.qbox.me", useHttps), makeHost("iovip-z0.qbox.me", useHttps)}
   103  	} else {
   104  		region.Up.Preferred = []string{makeHost(fmt.Sprintf("upload-%s.qiniup.com", regionID), useHttps), makeHost(fmt.Sprintf("up-%s.qiniup.com", regionID), useHttps)}
   105  		region.Io.Preferred = []string{makeHost(fmt.Sprintf("iovip-%s.qiniuio.com", regionID), useHttps)}
   106  	}
   107  	region.Rs.Preferred = []string{makeHost(fmt.Sprintf("rs-%s.qiniuapi.com", regionID), useHttps)}
   108  	region.Rsf.Preferred = []string{makeHost(fmt.Sprintf("rsf-%s.qiniuapi.com", regionID), useHttps)}
   109  	region.Api.Preferred = []string{makeHost(fmt.Sprintf("api-%s.qiniuapi.com", regionID), useHttps)}
   110  	region.Bucket.Preferred = []string{makeHost("uc.qiniuapi.com", useHttps), makeHost("kodo-config.qiniuapi.com", useHttps)}
   111  	region.Bucket.Alternative = []string{makeHost("uc.qbox.me", useHttps)}
   112  	return region
   113  }
   114  
   115  func makeHost(domain string, useHttps bool) string {
   116  	if useHttps {
   117  		return "https://" + domain
   118  	} else {
   119  		return "http://" + domain
   120  	}
   121  }
   122  
   123  func (r *Region) GetRegions(context.Context) ([]*Region, error) {
   124  	return []*Region{r}, nil
   125  }
   126  
   127  func (r *Region) Endpoints(serviceNames []ServiceName) (Endpoints, error) {
   128  	var endpoint Endpoints
   129  	for _, serviceName := range serviceNames {
   130  		switch serviceName {
   131  		case ServiceUp:
   132  			endpoint = endpoint.Join(r.Up)
   133  		case ServiceIo:
   134  			endpoint = endpoint.Join(r.Io)
   135  		case ServiceIoSrc:
   136  			endpoint = endpoint.Join(r.IoSrc)
   137  		case ServiceRs:
   138  			endpoint = endpoint.Join(r.Rs)
   139  		case ServiceRsf:
   140  			endpoint = endpoint.Join(r.Rsf)
   141  		case ServiceApi:
   142  			endpoint = endpoint.Join(r.Api)
   143  		case ServiceBucket:
   144  			endpoint = endpoint.Join(r.Bucket)
   145  		default:
   146  			return endpoint, ErrUnrecognizedServiceName
   147  		}
   148  	}
   149  	return endpoint, nil
   150  }
   151  
   152  func (r *Region) EndpointsIter(serviceNames []ServiceName) (*EndpointsIter, error) {
   153  	endpoints, err := r.Endpoints(serviceNames)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	return endpoints.Iter(), nil
   158  }
   159  
   160  func (r *Region) IsEqual(right *Region) bool {
   161  	return r.RegionID == right.RegionID &&
   162  		r.Up.IsEqual(right.Up) &&
   163  		r.Io.IsEqual(right.Io) &&
   164  		r.IoSrc.IsEqual(right.IoSrc) &&
   165  		r.Rs.IsEqual(right.Rs) &&
   166  		r.Rsf.IsEqual(right.Rsf) &&
   167  		r.Api.IsEqual(right.Api) &&
   168  		r.Bucket.IsEqual(right.Bucket)
   169  }
   170  
   171  func (ep Endpoints) Join(rights ...Endpoints) Endpoints {
   172  	newEndpoint := ep
   173  	for _, right := range rights {
   174  		newEndpoint.Accelerated = append(newEndpoint.Accelerated, right.Accelerated...)
   175  		newEndpoint.Preferred = append(newEndpoint.Preferred, right.Preferred...)
   176  		newEndpoint.Alternative = append(newEndpoint.Alternative, right.Alternative...)
   177  	}
   178  
   179  	return newEndpoint
   180  }
   181  
   182  func (ep Endpoints) IsEqual(right Endpoints) bool {
   183  	return reflect.DeepEqual(ep.Accelerated, right.Accelerated) &&
   184  		reflect.DeepEqual(ep.Preferred, right.Preferred) &&
   185  		reflect.DeepEqual(ep.Alternative, right.Alternative)
   186  }
   187  
   188  func (ep Endpoints) Iter() *EndpointsIter {
   189  	return &EndpointsIter{endpoints: ep}
   190  }
   191  
   192  func (ep Endpoints) HostsLength() int {
   193  	return len(ep.Accelerated) + len(ep.Preferred) + len(ep.Alternative)
   194  }
   195  
   196  func (ep Endpoints) IsEmpty() bool {
   197  	return len(ep.Accelerated) == 0 && len(ep.Preferred) == 0 && len(ep.Alternative) == 0
   198  }
   199  
   200  func (ep Endpoints) firstUrl(useHttps bool) string {
   201  	for _, accelerated := range ep.Accelerated {
   202  		return makeUrlFromHost(accelerated, useHttps)
   203  	}
   204  	for _, preferred := range ep.Preferred {
   205  		return makeUrlFromHost(preferred, useHttps)
   206  	}
   207  	for _, alternative := range ep.Alternative {
   208  		return makeUrlFromHost(alternative, useHttps)
   209  	}
   210  	return ""
   211  }
   212  
   213  func (ep Endpoints) GetEndpoints(context.Context) (Endpoints, error) {
   214  	return ep, nil
   215  }
   216  
   217  func (ep Endpoints) allUrls(useHttps bool) []string {
   218  	allHosts := make([]string, 0, len(ep.Accelerated)+len(ep.Preferred)+len(ep.Alternative))
   219  	for _, accelerated := range ep.Accelerated {
   220  		allHosts = append(allHosts, makeUrlFromHost(accelerated, useHttps))
   221  	}
   222  	for _, preferred := range ep.Preferred {
   223  		allHosts = append(allHosts, makeUrlFromHost(preferred, useHttps))
   224  	}
   225  	for _, alternative := range ep.Alternative {
   226  		allHosts = append(allHosts, makeUrlFromHost(alternative, useHttps))
   227  	}
   228  	return allHosts
   229  }
   230  
   231  func (ep Endpoints) ToHostProvider() hostprovider.HostProvider {
   232  	return &endpointsHostProvider{
   233  		iter:    ep.Iter(),
   234  		freezer: freezer.New(),
   235  	}
   236  }
   237  
   238  func (ep Endpoints) Clone() Endpoints {
   239  	return Endpoints{
   240  		Preferred:   append([]string{}, ep.Preferred...),
   241  		Alternative: append([]string{}, ep.Alternative...),
   242  		Accelerated: append([]string{}, ep.Accelerated...),
   243  	}
   244  }
   245  
   246  func makeUrlFromHost(host string, useHttps bool) string {
   247  	if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") {
   248  		return host
   249  	}
   250  	if useHttps {
   251  		return "https://" + host
   252  	} else {
   253  		return "http://" + host
   254  	}
   255  }
   256  
   257  func (iter *EndpointsIter) Next(nextHost *string) bool {
   258  	for {
   259  		switch iter.current {
   260  		case endpointsStatusAccelerated:
   261  			if iter.index >= len(iter.endpoints.Accelerated) {
   262  				iter.current = endpointsStatusPreferred
   263  				iter.index = 0
   264  				continue
   265  			}
   266  			host := iter.endpoints.Accelerated[iter.index]
   267  			iter.index += 1
   268  			*nextHost = host
   269  			return true
   270  		case endpointsStatusPreferred:
   271  			if iter.index >= len(iter.endpoints.Preferred) {
   272  				iter.current = endpointsStatusAlternative
   273  				iter.index = 0
   274  				continue
   275  			}
   276  			host := iter.endpoints.Preferred[iter.index]
   277  			iter.index += 1
   278  			*nextHost = host
   279  			return true
   280  		case endpointsStatusAlternative:
   281  			if iter.index >= len(iter.endpoints.Alternative) {
   282  				return false
   283  			}
   284  			host := iter.endpoints.Alternative[iter.index]
   285  			iter.index += 1
   286  			*nextHost = host
   287  			return true
   288  		default:
   289  			return false
   290  		}
   291  	}
   292  }
   293  
   294  func (iter *EndpointsIter) More() bool {
   295  	switch iter.current {
   296  	case endpointsStatusAccelerated:
   297  		if iter.index >= len(iter.endpoints.Accelerated) {
   298  			return len(iter.endpoints.Preferred) > 0
   299  		}
   300  		return true
   301  	case endpointsStatusPreferred:
   302  		if iter.index >= len(iter.endpoints.Preferred) {
   303  			return len(iter.endpoints.Alternative) > 0
   304  		}
   305  		return true
   306  	case endpointsStatusAlternative:
   307  		return iter.index < len(iter.endpoints.Alternative)
   308  	default:
   309  		return false
   310  	}
   311  }
   312  
   313  func (iter *EndpointsIter) SwitchToAlternative() {
   314  	if len(iter.endpoints.Alternative) > 0 && iter.current != endpointsStatusAlternative {
   315  		iter.current = endpointsStatusAlternative
   316  		iter.index = 0
   317  	}
   318  }
   319  
   320  func (iter *EndpointsIter) SwitchToPreferred() {
   321  	if len(iter.endpoints.Preferred) > 0 && iter.current != endpointsStatusPreferred {
   322  		iter.current = endpointsStatusPreferred
   323  		iter.index = 0
   324  	}
   325  }
   326  
   327  func (provider *endpointsHostProvider) Freeze(host string, cause error, duration time.Duration) error {
   328  	if duration <= 0 {
   329  		return nil
   330  	}
   331  
   332  	provider.lastFreezeErr = cause
   333  	return provider.freezer.Freeze(host, duration)
   334  }
   335  
   336  func (provider *endpointsHostProvider) Provider() (string, error) {
   337  	if provider.iter.endpoints.IsEmpty() {
   338  		return "", hostprovider.ErrNoHostFound
   339  	}
   340  
   341  	var host string
   342  	for provider.iter.Next(&host) {
   343  		if provider.freezer.Available(host) {
   344  			return host, nil
   345  		}
   346  	}
   347  
   348  	if provider.lastFreezeErr != nil {
   349  		return "", provider.lastFreezeErr
   350  	} else {
   351  		return "", hostprovider.ErrAllHostsFrozen
   352  	}
   353  }