創建http客戶端,請求其他服務接口(GET/POST)
阿新 • • 發佈:2017-08-19
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)