176 lines
5.9 KiB
Go
176 lines
5.9 KiB
Go
package platformapi
|
||
|
||
import (
|
||
"bytes"
|
||
"errors"
|
||
"io"
|
||
"io/ioutil"
|
||
"net"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"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 = 20 * 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(requestURL *url.URL, r *bytes.Buffer) string {
|
||
if strings.Index(requestURL.String(), "uploadImg") >= 0 {
|
||
return "binary content"
|
||
}
|
||
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()
|
||
trackID := utils.GetUUID()
|
||
baseapi.SugarLogger.Debugf("begin AccessPlatformAPIWithRetry:%s do url:%v", trackID, request.URL)
|
||
response, err := client.Do(request)
|
||
baseapi.SugarLogger.Debugf("end AccessPlatformAPIWithRetry:%s do url:%v, request:%s", trackID, request.URL, getClonedData(request.URL, savedBuf))
|
||
if err != nil {
|
||
baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s 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:%s access api url:%v, request:%v, error:%v", trackID, request.URL, getClonedData(request.URL, savedBuf), err)
|
||
return ErrAPIAccessFailed
|
||
}
|
||
}
|
||
usedMilliSecond := time.Now().Sub(beginTime) / time.Millisecond
|
||
if usedMilliSecond > 5000 {
|
||
baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s access api too slow, url:%v, request:%v, usedMilliSecond:%d", trackID, request.URL, getClonedData(request.URL, savedBuf), usedMilliSecond)
|
||
}
|
||
defer response.Body.Close()
|
||
if response.StatusCode != http.StatusOK {
|
||
// todo, hardcode,暂时在这里处理饿百的访问异常
|
||
if response.StatusCode == http.StatusBadRequest && request.URL.Hostname() == "api-be.ele.me" {
|
||
recoverableErrorRetryCount++
|
||
if recoverableErrorRetryCount <= config.MaxRecoverableRetryCount {
|
||
continue
|
||
}
|
||
}
|
||
if bodyData, err := ioutil.ReadAll(response.Body); err == nil {
|
||
baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s HTTP code is:%d, url:%v, request:%v, response:%s", trackID, response.StatusCode, request.URL, getClonedData(request.URL, savedBuf), string(bodyData))
|
||
} else {
|
||
baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s HTTP code is:%d, url:%v, request:%v", trackID, response.StatusCode, request.URL, getClonedData(request.URL, savedBuf))
|
||
}
|
||
return ErrHTTPCodeIsNot200
|
||
}
|
||
var (
|
||
errLevel string
|
||
bodyMap map[string]interface{}
|
||
)
|
||
bodyData, err := ioutil.ReadAll(response.Body)
|
||
if err != nil {
|
||
baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s url:%v, request:%v failed with error:%v", trackID, request.URL, getClonedData(request.URL, savedBuf), err)
|
||
} else if err = utils.TryUnmarshalUseNumber(bodyData, &bodyMap); err != nil {
|
||
baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s url:%v, request:%v failed with error:%v, bodyData:%s", trackID, request.URL, getClonedData(request.URL, savedBuf), err, string(bodyData))
|
||
}
|
||
|
||
if err != nil {
|
||
errLevel = ErrLevelRecoverableErr // 读取数据错误,或数据格式错误认为是偶发情况,重试
|
||
} else {
|
||
errLevel, err = handleResponse(bodyMap)
|
||
}
|
||
baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s url:%v, response:%s", trackID, request.URL, 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:%s failed, url:%v, response:%s, error:%v", trackID, request.URL, utils.Format4Output(bodyMap, true), err)
|
||
return err
|
||
}
|
||
}
|