Files
baseapi/platformapi/platformapi.go
gazebo 26f1d37caf - refactor RebuildError
- output store and sku info when mtwm api failed
2019-03-17 21:41:45 +08:00

194 lines
6.5 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"
"fmt"
"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:%s url:%v", trackID, request.Method, 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", trackID, 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
}
}
func RebuildError(inErr error, bzParams map[string]interface{}, watchKeys []string) (outErr error) {
if inErr != nil {
if codeErr, ok := inErr.(*utils.ErrorWithCode); ok {
appendErrList := []string{}
for _, key := range watchKeys {
if bzParams[key] != nil {
appendErrList = append(appendErrList, fmt.Sprintf("[%s:%v]", key, bzParams[key]))
}
}
if len(appendErrList) > 0 {
inErr = utils.NewErrorCode(strings.Join(appendErrList, ",")+", "+codeErr.ErrMsg(), codeErr.Code())
}
}
}
return inErr
}