1. 程式人生 > >創建http客戶端,請求其他服務接口(GET/POST)

創建http客戶端,請求其他服務接口(GET/POST)

useragent 解析 point ons 風格 獲取 turn cover init

service1擁有接口 :

GET user/{user_id}

POST user/add

service2調用service1的接口獲取數據

1.創建客戶端
//定義客戶端結構體
type SimpleClient struct {
    Client
    HTTPRequest  *http.Request  //用於返回執行時的request,直接設值無效。引用時註意做非空檢查。
    HTTPResponse *http.Response //用於返回執行結果。引用時註意做非空檢查。
}
 
//ClientConfig 客戶端屬性配置
type ClientConfig struct {
RequestID string
EndPointURLToken string //URL Token
OperationName string //鏈路追蹤名或調用函數名
Timeout int //單位為秒
NumMaxRetries int //最大重試次數
RetryMaxTimeDuration time.Duration //總計重試時間 ExpireTime
TraceOption *TraceOption //追蹤打印屬性
IsExternalURL bool //數據脫敏,默認為FALSE,是否為調用對外api,如是,則不在header的UserAgent中帶上內網ip.
IsRESTStatusCode bool //RESTFulAPI風格,使用狀態碼來表示資源態.
}

//Client 客戶端
type Client struct {
ctx context.Context
HTTPClient *http.Client
CustomHeader http.Header
CustomCookie *http.Cookie
EndPointURL string
ClientConfig
}
  //客戶端對象構造 func NewSimpleClient(ctx context.Context, url string, configs ...ClientConfig) *SimpleClient { c := &SimpleClient{} c.Client.ctx 
= ctx c.EndPointURL = url //初始化HTTPClient if len(configs) > 0 { for _, cf := range configs { c.ClientConfig = cf } if c.ClientConfig.Timeout <= 0 { c.ClientConfig.Timeout = 60 } } else {
  //默認配置 c.ClientConfig.NumMaxRetries
= 5 c.ClientConfig.RetryMaxTimeDuration = _MaxRetryTimeDuration c.ClientConfig.Timeout
= 60 } if c.ClientConfig.TraceOption == nil { c.ClientConfig.TraceOption = &TraceOption{} } if c.ClientConfig.OperationName != "" { c.OperationName = c.ClientConfig.OperationName } else { c.OperationName = c.EndPointURL } c.HTTPClient = GetHTTPClient(c.ClientConfig.Timeout) //初始化HTTP Header c.CustomHeader = http.Header{} c.initCustomHeader() // c.HTTPRequest = &http.Request{Header: http.Header{}} c.HTTPRequest = &http.Request{Header: c.CustomHeader} return c }

2.GET請求
//Get 發出Get請求
func (sc *SimpleClient) Get() ([]byte, error) {
    defer func() {
        if errPainc := recover(); errPainc != nil {
            stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n")
            logger.Error("[Get] -  stack errPainc:", errPainc)
            err := fmt.Errorf("[Get] recover stack::%s", stack)
            logger.Error("stack:", err)
        }
    }()
    sc.addHeaderRequestID()
    return sc.callGet()
}
//addHeaderRequestID 設置HTTP Header中的RequestID
func (c *Client) addHeaderRequestID() {
    if c.CustomHeader.Get(HeaderXKlookRequestID) != "" {
        return
    }
    if c.ClientConfig.RequestID == "" {
        c.ClientConfig.RequestID = utils.RequestIDFromContext(c.ctx)
    }
    c.CustomHeader.Add(HeaderXKlookRequestID, c.ClientConfig.RequestID)
}
func (sc *SimpleClient) callGet() ([]byte, error) {
    startTime := time.Now()
    sc.HTTPRequest, sc.HTTPResponse = nil, nil
    if sc.EndPointURL == "" {
        return nil, NewRequestErrorV1(sc.RequestID, sc.EndPointURL, "EndPointURL為空.")
    }

    client := sc.GetHTTPClient()
    var err1 error
    req, err := http.NewRequest("GET", sc.EndPointURL, nil)
    if err != nil {
        err1 = fmt.Errorf(" http.NewRequest發生異常. err:%s 耗時:%s ",
            err, time.Since(startTime))
        return nil, NewRequestErrorV1(sc.RequestID, sc.EndPointURL, err1.Error())
    }

    //設置HTTP Header
    copyHTTPHeader(req, sc.CustomHeader)
    if sc.CustomCookie != nil { //設置HTTP.Cookie
        req.AddCookie(sc.CustomCookie)
    }
    //如果是發給第三方的請求
        req.Header.Set(HeaderUserAgent, _DefaultUserAgent)
    
    sc.HTTPRequest = req
    resp, err := client.Do(req)
    if err != nil {
        err1 = fmt.Errorf("client.Do發生異常. err:%s 耗時:%s",
            err, time.Since(startTime))
        return nil, NewRequestErrorV2(sc.RequestID, sc.EndPointURL, err1.Error(), GetHTTPHeaderString(req))
    }
    sc.HTTPResponse = resp
    statusCode := resp.StatusCode
    //需要記錄每一次的Header信息
    if sc.ClientConfig.TraceOption.RequestHeader {
        logger.Info(sc.RequestID, " url:", sc.EndPointURL, " ", GetHTTPHeaderString(req),
            " statusCode:", statusCode)
    }

    //RESTFul API常用HTTP狀態碼來區分狀態,然後Body返回狀態詳情.此時,狀態碼為非200也是對的.
    if !sc.IsRESTStatusCode {
        if statusCode != http.StatusOK {

            msg := fmt.Sprintf("%s url:%s statusCode:%d %s", sc.RequestID, sc.EndPointURL,
                statusCode, GetHTTPHeaderString(req))
            if resp.Body != nil {
                respBody, err2 := ioutil.ReadAll(resp.Body)
                if err2 != nil {
                    msg += " 且讀取RespBody發生異常:" + err2.Error()
                } else {
                    msg += " RespBody:" + string(respBody)
                }
            }
            logger.Info(msg)

            if statusCode >= 400 && statusCode <= 505 {
                err1 = fmt.Errorf("對方服務出現異常狀態碼. 耗時:%s", time.Since(startTime))
            } else { //如302之類
                err1 = fmt.Errorf("對方服務返回非StatusOK狀態碼. 耗時:%s", time.Since(startTime))
            }
            resp.Body.Close()

            return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, err1.Error(),
                statusCode, GetHTTPHeaderString(req))
        }
    }

    if resp.Body == nil {
        resp.Body.Close()
        return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, "resp.Body為空.",
            statusCode, GetHTTPHeaderString(req))
    }

    respBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        if err == io.EOF {
            err1 = fmt.Errorf("resp.Body已讀到EOF. err:%s 耗時:%s", err, time.Since(startTime))
        } else {
            err1 = fmt.Errorf("ioutil.ReadAll讀取發生異常. err:%s 耗時:%s", err, time.Since(startTime))
        }
        resp.Body.Close()
        return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, err1.Error(),
            statusCode, GetHTTPHeaderString(req))
    }
    resp.Body.Close()

    if sc.ClientConfig.TraceOption.RespBody { //記錄返回包體
        logger.Info(sc.RequestID, " url:", sc.EndPointURL, " RespBody:", string(respBody))
    }

    //
    //IsRESTFulStatusCode 解析例子
    // retErr, ok := err.(*rest.RequestError)
    // if ok {
    //     t.Log(" \nStatusCode:", retErr.StatusCode(), "\nerr:", retErr.ErrMessage())
    // }
    //
    if sc.IsRESTStatusCode && statusCode != http.StatusOK {
        //當為REST風格,且狀態碼不為200時,返回Body,及Error,調用方可從Error中取得返回狀態碼
        return respBody, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, "",
            statusCode, GetHTTPHeaderString(req))
    }

    return respBody, nil
}
3.POST請求
//PostAndParseResult 發出Post請求,並得到標準化的結構體
func (sc *SimpleClient) PostAndParseResult(body []byte) (*KLResultJSON, error) {
    defer func() {
        if errPainc := recover(); errPainc != nil {
            stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n")
            logger.Error("[PostAndParseResult] -  stack errPainc:", errPainc)
            err := fmt.Errorf("[PostAndParseResult] recover stack::%s", stack)
            logger.Error("stack:", err)
        }
    }()

    sc.addHeaderRequestID()
    body, err := sc.callPost(body)
    if err != nil {
        return nil, err
    }
    return ParseResultJSON(body)
}
func (sc *SimpleClient) callPost(body []byte) ([]byte, error) {
    return sc.callHTTPUpdateRequest("POST", body)
}
//callHTTPUpdateRequest post/put執行函數實現
func (sc *SimpleClient) callHTTPUpdateRequest(httpMetchod string, body []byte) ([]byte, error) {
    startTime := time.Now()

    if sc.EndPointURL == "" {
        return nil, NewRequestErrorV1(sc.RequestID, sc.EndPointURL, "EndPointURL為空.")
    }

    if httpMetchod != "DELETE" && body == nil {
        return nil, NewRequestErrorV1(sc.RequestID, sc.EndPointURL, "body為空.")
    }

    sc.HTTPRequest, sc.HTTPResponse = nil, nil
    client := sc.GetHTTPClient()
    if sc.ClientConfig.TraceOption.RequestBody {
        logger.Info(sc.RequestID, " url:", sc.EndPointURL, " RequestBody:", string(body))
    }

    var err, err1 error
    var req *http.Request
    if httpMetchod == "DELETE" {
        req, err = http.NewRequest(httpMetchod, sc.EndPointURL, nil)
    } else {
        req, err = http.NewRequest(httpMetchod, sc.EndPointURL, bytes.NewReader(body))
    }
    if err != nil {
        err1 = fmt.Errorf("http.NewRequest發生異常. err:%s 耗時:%s ",
            err, time.Since(startTime))
        return nil, NewRequestErrorV1(sc.RequestID, sc.EndPointURL, err1.Error())
    }

    //設置HTTP Header
    copyHTTPHeader(req, sc.CustomHeader)
    if sc.CustomCookie != nil { //設置HTTP.Cookie
        req.AddCookie(sc.CustomCookie)
    }
        req.Header.Set(HeaderUserAgent, _DefaultUserAgent)
    
    sc.HTTPRequest = req
    resp, err := client.Do(req)
    if err != nil {
        err1 = fmt.Errorf("client.Do發生異常. err:%s 耗時:%s ",
            err.Error(), time.Since(startTime))
        return nil, NewRequestErrorV2(sc.RequestID, sc.EndPointURL, err1.Error(),
            GetHTTPHeaderString(req))
    }
    defer resp.Body.Close()
    sc.HTTPResponse = resp
    statusCode := resp.StatusCode

    //需要記錄每一次的Header信息
    if sc.ClientConfig.TraceOption.RequestHeader {
        logger.Info(sc.RequestID, " url:", sc.EndPointURL, " ", GetHTTPHeaderString(req),
            " statusCode:", statusCode, " status:", resp.Status)
    }

    //RESTFul API常用HTTP狀態碼來區分狀態,然後Body返回狀態詳情.此時,狀態碼為非200也是對的.
    if !sc.IsRESTStatusCode {
        if statusCode != http.StatusOK {

            msg := fmt.Sprintf("%s url:%s statusCode:%d %s", sc.RequestID, sc.EndPointURL,
                statusCode, GetHTTPHeaderString(req))
            if resp.Body != nil {
                respBody, err2 := ioutil.ReadAll(resp.Body)
                if err2 != nil {
                    msg += " 且讀取RespBody發生異常:" + err2.Error()
                } else {
                    msg += " RespBody:" + string(respBody)
                }
            }
            logger.Info(msg)
            if statusCode >= 400 && statusCode <= 505 {
                err1 = fmt.Errorf("對方服務出現異常. 耗時:%s", time.Since(startTime))
                return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, err1.Error(),
                    statusCode, GetHTTPHeaderString(req))
            }
            // 在Post情況下,當狀態碼不為StatusOK時,算執行失敗。
            err1 = fmt.Errorf("返回的狀態碼非StatusOK. 耗時:%s", time.Since(startTime))
            return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, err1.Error(),
                statusCode, GetHTTPHeaderString(req))
        }
    }

    if resp.Body == nil {
        err1 = fmt.Errorf("resp.Body為空. err:%v  耗時:%s",
            err, time.Since(startTime))
        return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, err1.Error(),
            statusCode, GetHTTPHeaderString(req))
    }

    respBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        if err == io.EOF {
            err1 = fmt.Errorf("resp.Body已讀到EOF. err:%s 耗時:%s",
                err, time.Since(startTime))
        } else {
            err1 = fmt.Errorf("ioutil.ReadAll讀取發生異常. err:%s  耗時:%s",
                err, time.Since(startTime))
        }
        return nil, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, err1.Error(),
            statusCode, GetHTTPHeaderString(req))
    }

    if sc.ClientConfig.TraceOption.RespBody {
        logger.Info(sc.RequestID, " url:", sc.EndPointURL, " RespBody:", string(respBody))
    }

    //
    //IsRESTFulStatusCode 解析例子
    // retErr, ok := err.(*rest.RequestError)
    // if ok {
    //     t.Log(" \nStatusCode:", retErr.StatusCode(), "\nerr:", retErr.ErrMessage())
    // }
    //
    if sc.IsRESTStatusCode && statusCode != http.StatusOK {
        //當為REST風格,且狀態碼不為200時,返回Body,及Error,調用方可從Error中取得返回狀態碼
        return respBody, NewRequestErrorV3(sc.RequestID, sc.EndPointURL, "",
            statusCode, GetHTTPHeaderString(req))
    }

    return respBody, nil
}

創建http客戶端,請求其他服務接口(GET/POST)