package http import ( "bytes" "encoding/json" "encoding/xml" "errors" "fmt" "gitlab.ecloud.com/ecloud/ecloudsdkcore/auth" "gitlab.ecloud.com/ecloud/ecloudsdkcore/auth/provider" "gitlab.ecloud.com/ecloud/ecloudsdkcore/errs" "gitlab.ecloud.com/ecloud/ecloudsdkcore/request" "gitlab.ecloud.com/ecloud/ecloudsdkcore/response" "gitlab.ecloud.com/ecloud/ecloudsdkcore/utils" "io/ioutil" "net/http" "net/url" "strings" "sync" ) type NetHttpClient struct { } type RetryFunc func(request *request.HttpRequest, returnType *interface{}) (*response.HttpResponse, error) func NewHttpClient() *NetHttpClient { return &NetHttpClient{} } var clientPool = &sync.Map{} // doRequest do the request. func (hc *NetHttpClient) getClient(request *request.HttpRequest, cm map[string]interface{}) (*http.Client, error) { var host string if cm["ClientProxyHost"] != nil && cm["ClientProxyPort"] != nil { host = fmt.Sprintf("%s:%s", *cm["ClientProxyHost"].(*string), *cm["ClientProxyPort"].(*string)) } else { url, err := url.Parse(request.Url) if err != nil { return nil, errs.NewInvalidParameterError(fmt.Sprintf("request url invalid, url=: %s", request.Url), err) } host = url.Host } client, ok := clientPool.Load(host) if client == nil && !ok { var err error client, err = hc.buildHttpClient(cm) if err != nil { return nil, err } clientPool.Store(host, client) } return client.(*http.Client), nil } func (hc *NetHttpClient) Execute(hr *request.HttpRequest, cm map[string]interface{}, rt interface{}) (*response.HttpResponse, error) { req, err := hc.buildRequest(hr, cm) if err != nil { return nil, err } client, err := hc.getClient(hr, cm) if err != nil { return nil, errs.NewServerRequestError("get http error: ", err) } resp, err := client.Do(req) if err != nil { return nil, errs.NewServerRequestError(err.Error(), err) } if resp == nil { return nil, errs.NewServerResponseError("response is nil", nil, -1, nil, "") } if err = handleResponse(resp, cm, rt); err != nil { return nil, err } return &response.HttpResponse{ StatusCode: resp.StatusCode, Headers: resp.Header, Data: resp, }, nil } func (hc *NetHttpClient) buildHttpClient(cm map[string]interface{}) (*http.Client, error) { hcb := NewHttpClientBuilder() if utils.IsSet(cm["ReadTimeout"]) { hcb.SetReadTimeout(utils.Int32Value(cm["ReadTimeout"].(*int32))) } if utils.IsSet(cm["ConnectTimeout"]) { hcb.SetConnectTimeout(utils.Int32Value(cm["ConnectTimeout"].(*int32))) } if utils.IsSet(cm["ClientProxyHost"]) { proxy := Proxy{ Protocol: cm["ClientProxyProtocol"].(*string), Host: cm["ClientProxyHost"].(*string), Port: cm["ClientProxyPort"].(*int32), Username: cm["ClientProxyUsername"].(*string), Password: cm["ClientProxyPassword"].(*string), } hcb.SetClientProxy(proxy) } ignore := true if utils.IsSet(cm["IgnoreSSL"]) { ignore = utils.BoolValue(cm["IgnoreSSL"].(*bool)) } certFile := "" if utils.IsSet(cm["CertFile"]) { certFile = utils.StringValue(cm["CertFile"].(*string)) } clientCertFile := "" if utils.IsSet(cm["ClientCertFile"]) { clientCertFile = utils.StringValue(cm["ClientCertFile"].(*string)) } clientKeyFile := "" if utils.IsSet(cm["ClientKeyFile"]) { clientKeyFile = utils.StringValue(cm["ClientKeyFile"].(*string)) } err := hcb.ApplySSLSettings(ignore, certFile, clientCertFile, clientKeyFile) if err != nil { return nil, err } return hcb.Build(), nil } func handleResponse(resp *http.Response, cm map[string]interface{}, returnType interface{}) error { body, err := ioutil.ReadAll(resp.Body) if err != nil { if resp.Body != nil { _ = resp.Body.Close() } return errs.NewServerResponseError(fmt.Sprintf("response status is: %s,status code is:%d", resp.Status, resp.StatusCode), err, resp.StatusCode, resp.Header, "can not read response body") } // successful if isSuccessful(resp.StatusCode) { if returnType == nil || resp.StatusCode == 204 { if resp.Body != nil { _ = resp.Body.Close() } return nil } // If we succeed, return the data, otherwise pass on to deserialize error. err = deserialize(returnType, body, cm, resp.Header.Get("Content-Type")) if err != nil { return errs.NewServerResponseError(fmt.Sprintf("can't deserialize response body with content-type: %s,response body:%s", resp.Header.Get("Content-Type"), string(body)), err, resp.StatusCode, resp.Header, string(body)) } } else { if body == nil { return errs.NewServerResponseError(fmt.Sprintf("response status is: %s,status code is:%d", resp.Status, resp.StatusCode), nil, resp.StatusCode, resp.Header, "response body is nil") } respBody := string(body) return errs.NewServerResponseError(fmt.Sprintf("response status: %s,http status code:%d,response body:%s", resp.Status, resp.StatusCode, respBody), nil, resp.StatusCode, resp.Header, respBody) } return nil } func isSuccessful(code int) bool { return code >= 200 && code < 300 } func (hc *NetHttpClient) buildRequest(hr *request.HttpRequest, cm map[string]interface{}) (request *http.Request, err error) { req, err := prepareRequest(hr, cm) if err != nil { return nil, err } return req, nil } // prepareRequest build the request func prepareRequest(hr *request.HttpRequest, cm map[string]interface{}) (req *http.Request, err error) { var body *bytes.Buffer rawBody := hr.Body contentType := hr.ContentType // Detect rawBody if rawBody != nil { if utils.IsSet(cm["Provider"]) { provider := cm["Provider"].(provider.ICredentialProvider) credential, err := provider.GetCredential() if err != nil { return nil, err } if *credential.CredentialType == auth.CredentialMop && *credential.EncryptionType == auth.EncrytMopRsa { bodyBuf := &bytes.Buffer{} err := json.NewEncoder(bodyBuf).Encode(rawBody) if err != nil { return nil, err } encryptContent, err := auth.GetCredentialManager(*credential.CredentialType).Encrypt(bodyBuf.Bytes(), *credential.PublicKey) if err != nil { return nil, err } rawBody = encryptContent } } contentType = utils.DetectContentType(rawBody) body, err = utils.SetBody(rawBody, contentType) if err != nil { return nil, errs.NewServerRequestError(fmt.Sprintf("set request body error: %s", err.Error()), err) } } // Setup url and query parameters, url contains query strings rawUrl := hr.Url if len(rawUrl) == 0 { return nil, errs.NewServerRequestError("request url is empty", nil) } realUrl, err := url.Parse(rawUrl) if err != nil { return nil, errs.NewServerRequestError(fmt.Sprintf("can't parse request url: %s, "+ "error is: %s", rawUrl, err.Error()), err) } // Generate a new http.Request method := hr.Method if body != nil { req, err = http.NewRequest(method, realUrl.String(), body) } else { req, err = http.NewRequest(method, realUrl.String(), nil) } if err != nil { return nil, errs.NewServerRequestError(fmt.Sprintf("can't create http request, "+ "method=%s, url=%s, error: %s", method, realUrl.String(), err.Error()), err) } // Add request headers headers := hr.HeaderParams for name, value := range headers { if name == "Host" { req.Host = value continue } req.Header.Add(name, value) } if len(contentType) == 0 { contentType = "application/json; charset=utf-8" } req.Header.Add("Content-Type", contentType) return req, nil } func deserialize(respType interface{}, respBody []byte, configMap map[string]interface{}, contentType string) (err error) { if respBody == nil { //return errs.NewGenericResponseError("response body is nil", nil, respBody) return errs.NewServerResponseError("response body is nil", nil, -1, nil, "") } if utils.IsSet(configMap["Provider"]) { provider := configMap["Provider"].(provider.ICredentialProvider) credential, err := provider.GetCredential() if err != nil { return err } if *credential.CredentialType == auth.CredentialMop && *credential.EncryptionType == auth.EncrytMopRsa { decryptContent, err := auth.GetCredentialManager(*credential.CredentialType).Decrypt(string(respBody), *credential.PrivateKey) if err != nil { return err } respBody = utils.StringToBytes(decryptContent) } } if strings.Contains(contentType, "application/xml") { if err = xml.Unmarshal(respBody, respType); err != nil { return errs.NewServerResponseError(fmt.Sprintf("can't deserialize response body with: %s", string(respBody)), err, -1, nil, string(respBody)) } return nil } else if strings.Contains(contentType, "json") { if err = json.Unmarshal(respBody, respType); err != nil { return errs.NewServerResponseError(fmt.Sprintf("can't deserialize response body with: %s", string(respBody)), err, -1, nil, string(respBody)) } return nil } return errors.New("undefined response type") }