153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
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)
|
|
}
|
|
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:%v, error:%v", request.URL, getClonedData(savedBuf), response, err)
|
|
return err
|
|
}
|
|
}
|