package platformapi import ( "bytes" "errors" "io" "io/ioutil" "net" "net/http" "time" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/utils" "github.com/fatih/structs" ) const ( DefClientTimeout = 10 * time.Second DefSleepSecondWhenExceedLimit = 3 * time.Second DefRandSlice = 10 DefMaxRecoverableRetryCount = 3 DefMaxExceedLimitRetryCount = 25 ) type APIRetryConfig struct { MaxExceedLimitRetryCount int MaxRecoverableRetryCount int SleepSecondWhenExceedLimit time.Duration } type APIConfig struct { APIRetryConfig ClientTimeout time.Duration } type AccessPlatformAPIWithRetryParam struct { APIRetryConfig Client *http.Client Request *http.Request } var ( DefAPIConfig = APIConfig{ APIRetryConfig: APIRetryConfig{ MaxExceedLimitRetryCount: DefMaxExceedLimitRetryCount, MaxRecoverableRetryCount: DefMaxRecoverableRetryCount, SleepSecondWhenExceedLimit: DefSleepSecondWhenExceedLimit, }, ClientTimeout: DefClientTimeout, } ) const ( ErrLevelSuccess = "JXC4_SUCCESS" ErrLevelExceedLimit = "JXC4_EXCEED_LIMIT" ErrLevelRecoverableErr = "JXC4_RECOVERABLE" ErrLevelGeneralFail = "JXC4_GENERAL_FAIL" ErrLevelCodeIsNotOK = "JXC4_CODE_IS_NOT_OK" ) const ( maxDataSizeDontOutput = 200 * 1024 ) // common api access error var ( ErrAPIAccessFailed = errors.New("access API failed") ErrHTTPCodeIsNot200 = errors.New("HTTP code is not 200") ErrResponseDataFormatWrong = errors.New("the data of response has wrong format") ) // common callback response var ( ErrStrUnescapeError = "can not unescape data:%v, error:%v" ErrStrUnmarshalError = "can not unmarshal data:%v, error:%v" ErrStrCallbackSignatureIsWrong = "wrong signature" ) func init() { structs.DefaultTagName = "json" } func getClonedData(r *bytes.Buffer) string { retVal := string(r.Bytes()) if len(retVal) > maxDataSizeDontOutput { return "request data is too large" } return retVal } func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.Request, config *APIConfig, handleResponse func(bodyMap map[string]interface{}) (string, error)) error { exceedLimitRetryCount := 0 recoverableErrorRetryCount := 0 for { savedBuf := new(bytes.Buffer) request := handleRequest() if request.Body != nil { request.Body = ioutil.NopCloser(io.TeeReader(request.Body, savedBuf)) } beginTime := time.Now() // baseapi.SugarLogger.Debugf("begin AccessPlatformAPIWithRetry url:%v", request.URL) response, err := client.Do(request) // baseapi.SugarLogger.Debugf("end AccessPlatformAPIWithRetry url:%v, request:%s", request.URL, getClonedData(savedBuf)) if err != nil { baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry client.Get return err:%v", err) err, ok := err.(net.Error) if ok && err.Timeout() && recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { recoverableErrorRetryCount++ continue } else { baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry access api url:%v, request:%v, error:%v", request.URL, getClonedData(savedBuf), err) return ErrAPIAccessFailed } } usedMilliSecond := time.Now().Sub(beginTime) / time.Millisecond if usedMilliSecond > 5000 { baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry access api too slow, url:%v, request:%v, usedMilliSecond:%d", request.URL, getClonedData(savedBuf), usedMilliSecond) } defer response.Body.Close() if response.StatusCode != 200 { if bodyData, err := ioutil.ReadAll(response.Body); err == nil { baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry HTTP code is:%d, url:%v, request:%v, response:%s", response.StatusCode, request.URL, getClonedData(savedBuf), string(bodyData)) } else { baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry HTTP code is:%d, url:%v, request:%v", response.StatusCode, request.URL, getClonedData(savedBuf)) } return ErrHTTPCodeIsNot200 } var errLevel string bodyMap, err := utils.HTTPResponse2Json(response) if err != nil { errLevel = ErrLevelRecoverableErr } else { errLevel, err = handleResponse(bodyMap) } // baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry url:%v, request:%v, response:%s", request.URL, getClonedData(savedBuf), utils.Format4Output(bodyMap, true)) if err == nil { return nil } else if errLevel == ErrLevelExceedLimit { exceedLimitRetryCount++ if exceedLimitRetryCount <= config.MaxExceedLimitRetryCount { time.Sleep(config.SleepSecondWhenExceedLimit) continue } } else if errLevel == ErrLevelRecoverableErr { recoverableErrorRetryCount++ if recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { continue } } baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry failed, url:%v, request:%v, response:%s, error:%v", request.URL, getClonedData(savedBuf), utils.Format4Output(bodyMap, true), err) return err } }