package clientv2 import ( "github.com/qiniu/go-sdk/v7/internal/hostprovider" "net/http" "net/url" "strings" "time" ) type HostsRetryConfig struct { RetryConfig RetryConfig // 主备域名重试参数 HostFreezeDuration time.Duration // 主备域名冻结时间(默认:600s),当一个域名请求失败被冻结的时间,最小 time.Millisecond HostProvider hostprovider.HostProvider // 备用域名获取方法 ShouldFreezeHost func(req *http.Request, resp *http.Response, err error) bool } func (c *HostsRetryConfig) init() { if c.RetryConfig.ShouldRetry == nil { c.RetryConfig.ShouldRetry = func(req *http.Request, resp *http.Response, err error) bool { return isHostRetryable(req, resp, err) } } if c.RetryConfig.RetryMax < 0 { c.RetryConfig.RetryMax = 1 } c.RetryConfig.init() if c.HostFreezeDuration < time.Millisecond { c.HostFreezeDuration = 600 * time.Second } if c.ShouldFreezeHost == nil { c.ShouldFreezeHost = func(req *http.Request, resp *http.Response, err error) bool { return true } } } type hostsRetryInterceptor struct { options HostsRetryConfig } func NewHostsRetryInterceptor(options HostsRetryConfig) Interceptor { return &hostsRetryInterceptor{ options: options, } } func (interceptor *hostsRetryInterceptor) Priority() InterceptorPriority { return InterceptorPriorityRetryHosts } func (interceptor *hostsRetryInterceptor) Intercept(req *http.Request, handler Handler) (resp *http.Response, err error) { if interceptor == nil || req == nil { return handler(req) } interceptor.options.init() // 不重试 if interceptor.options.RetryConfig.RetryMax <= 0 { return handler(req) } for i := 0; ; i++ { // Clone 防止后面 Handler 处理对 req 有污染 reqBefore := cloneReq(req.Context(), req) resp, err = handler(req) if !interceptor.options.RetryConfig.ShouldRetry(reqBefore, resp, err) { return resp, err } // 尝试冻结域名 oldHost := req.URL.Host if interceptor.options.ShouldFreezeHost(req, resp, err) { if fErr := interceptor.options.HostProvider.Freeze(oldHost, err, interceptor.options.HostFreezeDuration); fErr != nil { break } } if i >= interceptor.options.RetryConfig.RetryMax { break } // 尝试更换域名 newHost, pErr := interceptor.options.HostProvider.Provider() if pErr != nil { break } if len(newHost) == 0 { break } if newHost != oldHost { urlString := req.URL.String() urlString = strings.Replace(urlString, oldHost, newHost, 1) u, ppErr := url.Parse(urlString) if ppErr != nil { break } reqBefore.Host = u.Host reqBefore.URL = u } req = reqBefore retryInterval := interceptor.options.RetryConfig.RetryInterval() if retryInterval < time.Microsecond { continue } time.Sleep(retryInterval) } return resp, err } func isHostRetryable(req *http.Request, resp *http.Response, err error) bool { return isRequestRetryable(req) && (isResponseRetryable(resp) || IsErrorRetryable(err)) }