Files
baseapi/platformapi/platformapi.go
2019-03-28 22:57:47 +08:00

195 lines
6.7 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"
)
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 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, ioutil.ReadAll failed with error:%v", trackID, response.StatusCode, request.URL, getClonedData(request.URL, savedBuf), err)
}
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 {
const maxOutputLen = 200
bodyDataLen := len(bodyData)
bodyData2 := bodyData
if bodyDataLen > maxOutputLen {
bodyData2 = bodyData2[:maxOutputLen]
}
baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s url:%v, request:%v failed with error:%v, bodyData:%s", trackID, request.URL, getClonedData(request.URL, savedBuf), err, string(bodyData2))
}
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
}