fix: json request 支持自定义匹配err

This commit is contained in:
Qu Xuan
2020-07-06 21:18:10 +08:00
parent acdc1dafd3
commit 6ac6768e36
4 changed files with 190 additions and 4 deletions

View File

@@ -24,6 +24,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth"
@@ -133,6 +134,34 @@ func (self *SBaseManager) _get(request requests.IRequest, responseKey string) (j
return self._do(request, responseKey)
}
type HuaweiClientError struct {
Code int
Errorcode []string
err error
Details string
}
func (ce *HuaweiClientError) Error() string {
return jsonutils.Marshal(ce).String()
}
func (ce *HuaweiClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error {
err := body.Unmarshal(ce)
if err != nil {
ce.err = errors.Wrapf(err, "body.Unmarshal(%s)", body.String())
ce.Code = statusCode
ce.Details = body.String()
return ce
}
if ce.Code == 0 {
ce.Code = statusCode
}
if len(ce.Details) == 0 {
ce.Details = body.String()
}
return ce
}
func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, jsonutils.JSONObject, error) {
ctx := context.Background()
// hook request
@@ -162,11 +191,15 @@ func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, j
log.Debugf("url: %s", request.BuildUrl())
}
client := httputils.NewJsonClient(self.httpClient)
req := httputils.NewJsonRequest(httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), jsonBody)
req.SetHeader(header)
resp := &HuaweiClientError{}
// 发送 request。todo: 支持debug
const MAX_RETRY = 3
retry := MAX_RETRY
for {
h, b, e := httputils.JSONRequest(self.httpClient, ctx, httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), header, jsonBody, self.debug)
h, b, e := client.Send(ctx, req, resp, self.debug)
if e == nil {
if self.debug {
log.Debugf("response: %s body: %s", h, b)
@@ -175,12 +208,12 @@ func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, j
}
switch err := e.(type) {
case *httputils.JSONClientError:
case *HuaweiClientError:
if err.Code == 499 && retry > 0 && request.GetMethod() == "GET" {
retry -= 1
time.Sleep(time.Second * time.Duration(MAX_RETRY-retry))
} else if (err.Code == 404 || strings.Contains(err.Details, "could not be found") || strings.Contains(err.Details, "does not exist")) && request.GetMethod() != "POST" {
return h, b, cloudprovider.ErrNotFound
return h, b, errors.Wrap(cloudprovider.ErrNotFound, err.Error())
} else {
return h, b, e
}

View File

@@ -19,6 +19,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud/huawei/client/modules"
)
type SLink struct {
@@ -182,6 +183,10 @@ func (self *SHuaweiClient) CreateClouduser(name, password, desc string) (*SCloud
user := SClouduser{client: self}
err = DoCreate(client.Users.Create, jsonutils.Marshal(map[string]interface{}{"user": params}), &user)
if err != nil {
ce, ok := err.(*modules.HuaweiClientError)
if ok && len(ce.Errorcode) > 0 && ce.Errorcode[0] == "1101" {
return nil, errors.Wrap(err, `IAM user name. The length is between 5 and 32. The first digit is not a number. Special characters can only contain the '_' '-' or ' '`) //https://support.huaweicloud.com/api-iam/iam_08_0015.html
}
return nil, errors.Wrap(err, "DoCreate")
}
return &user, nil

View File

@@ -239,7 +239,7 @@ func (region *SRegion) GetNatDEntryByID(id string) (SNatDEntry, error) {
err := DoGet(region.ecsClient.DNatRules.Get, id, map[string]string{}, &dnat)
if err != nil {
return SNatDEntry{}, cloudprovider.ErrNotFound
return SNatDEntry{}, err
}
return dnat, nil
}

View File

@@ -81,6 +81,89 @@ type JSONClientErrorMsg struct {
Error *JSONClientError
}
type JsonClient struct {
client *http.Client
}
type JsonReuest interface {
GetHttpMethod() THttpMethod
GetRequestBody() jsonutils.JSONObject
GetUrl() string
SetHttpMethod(method THttpMethod)
GetHeader() http.Header
SetHeader(header http.Header)
}
type JsonBaseRequest struct {
httpMethod THttpMethod
url string
params interface{}
header http.Header
}
func (req *JsonBaseRequest) GetHttpMethod() THttpMethod {
return req.httpMethod
}
func (req *JsonBaseRequest) GetRequestBody() jsonutils.JSONObject {
if req.params != nil {
return jsonutils.Marshal(req.params)
}
return nil
}
func (req *JsonBaseRequest) GetUrl() string {
return req.url
}
func (req *JsonBaseRequest) SetHttpMethod(method THttpMethod) {
req.httpMethod = method
}
func (req *JsonBaseRequest) GetHeader() http.Header {
return req.header
}
func (req *JsonBaseRequest) SetHeader(header http.Header) {
for k, values := range header {
req.header.Del(k)
for _, v := range values {
req.header.Add(k, v)
}
}
}
func NewJsonRequest(method THttpMethod, url string, params interface{}) *JsonBaseRequest {
return &JsonBaseRequest{
httpMethod: method,
url: url,
params: params,
header: http.Header{"Content-Type": []string{"application/json"}},
}
}
type JsonResponse interface {
ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error
}
func (ce *JSONClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error {
err := body.Unmarshal(ce)
if err != nil {
return errors.Wrapf(err, "body.Unmarshal(%s)", body.String())
}
if ce.Code != 0 || len(ce.Class) > 0 || len(ce.Class) > 0 || len(ce.Details) > 0 {
if ce.Code == 0 {
ce.Code = statusCode
}
return ce
}
return nil
}
func NewJsonClient(client *http.Client) *JsonClient {
return &JsonClient{client: client}
}
func (e *JSONClientError) Error() string {
errMsg := JSONClientErrorMsg{Error: e}
return jsonutils.Marshal(errMsg).String()
@@ -383,6 +466,71 @@ func CloseResponse(resp *http.Response) {
}
}
func (client *JsonClient) Send(ctx context.Context, req JsonReuest, response JsonResponse, debug bool) (http.Header, jsonutils.JSONObject, error) {
var bodystr string
body := req.GetRequestBody()
if !gotypes.IsNil(body) {
bodystr = body.String()
}
jbody := strings.NewReader(bodystr)
resp, err := Request(client.client, ctx, req.GetHttpMethod(), req.GetUrl(), req.GetHeader(), jbody, debug)
if err != nil {
ce := &JSONClientError{}
ce.Code = 499
ce.Details = err.Error()
return nil, nil, ce
}
defer CloseResponse(resp)
if debug {
dump, _ := httputil.DumpResponse(resp, false)
if resp.StatusCode < 300 {
green(string(dump))
} else if resp.StatusCode < 400 {
yellow(string(dump))
} else {
red(string(dump))
}
}
rbody, err := ioutil.ReadAll(resp.Body)
if debug {
fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody))
}
if err != nil {
ce := &JSONClientError{}
ce.Code = resp.StatusCode
ce.Details = fmt.Sprintf("Fail to read body: %v", err)
return resp.Header, nil, ce
}
var jrbody jsonutils.JSONObject = nil
if len(rbody) > 0 && string(rbody[0]) == "{" {
var err error
jrbody, err = jsonutils.Parse(rbody)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "parsing json %s failed: %v", string(rbody), err)
}
ce := &JSONClientError{}
ce.Code = resp.StatusCode
ce.Details = fmt.Sprintf("jsonutils.Parse(%s) error: %v", string(rbody), err)
return resp.Header, nil, ce
}
} else {
jrbody = jsonutils.NewDict()
}
if resp.StatusCode < 300 {
return resp.Header, jrbody, nil
} else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
ce := JSONClientError{}
ce.Code = resp.StatusCode
ce.Details = resp.Header.Get("Location")
ce.Class = "redirect"
return resp.Header, nil, &ce
}
return resp.Header, jrbody, response.ParseErrorFromJsonResponse(resp.StatusCode, jrbody)
}
func ParseResponse(resp *http.Response, err error, debug bool) (http.Header, []byte, error) {
if err != nil {
ce := JSONClientError{}