Files
baseapi/platformapi/platformapi.go
2019-01-23 10:43:40 +08:00

154 lines
4.8 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)
}
// 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
}
}