Files
baseapi/platformapi/platformapi.go

167 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, err := utils.HTTPResponse2Json(response)
if err != nil {
errLevel = ErrLevelRecoverableErr // 京东到家偶尔会返回HTML重试
} 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
}
}