1package common 2 3import ( 4 "encoding/hex" 5 "encoding/json" 6 "fmt" 7 "log" 8 "net/http" 9 "net/http/httputil" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 15 tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http" 16 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" 17) 18 19type Client struct { 20 region string 21 httpClient *http.Client 22 httpProfile *profile.HttpProfile 23 profile *profile.ClientProfile 24 credential *Credential 25 signMethod string 26 unsignedPayload bool 27 debug bool 28} 29 30func (c *Client) Send(request tchttp.Request, response tchttp.Response) (err error) { 31 if request.GetDomain() == "" { 32 domain := c.httpProfile.Endpoint 33 if domain == "" { 34 domain = tchttp.GetServiceDomain(request.GetService()) 35 } 36 request.SetDomain(domain) 37 } 38 39 if request.GetHttpMethod() == "" { 40 request.SetHttpMethod(c.httpProfile.ReqMethod) 41 } 42 43 tchttp.CompleteCommonParams(request, c.GetRegion()) 44 45 if c.signMethod == "HmacSHA1" || c.signMethod == "HmacSHA256" { 46 return c.sendWithSignatureV1(request, response) 47 } else { 48 return c.sendWithSignatureV3(request, response) 49 } 50} 51 52func (c *Client) sendWithSignatureV1(request tchttp.Request, response tchttp.Response) (err error) { 53 // TODO: not an elegant way, it should be done in common params, but finally it need to refactor 54 request.GetParams()["Language"] = c.profile.Language 55 err = tchttp.ConstructParams(request) 56 if err != nil { 57 return err 58 } 59 err = signRequest(request, c.credential, c.signMethod) 60 if err != nil { 61 return err 62 } 63 httpRequest, err := http.NewRequest(request.GetHttpMethod(), request.GetUrl(), request.GetBodyReader()) 64 if err != nil { 65 return err 66 } 67 if request.GetHttpMethod() == "POST" { 68 httpRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"} 69 } 70 if c.debug { 71 outbytes, err := httputil.DumpRequest(httpRequest, true) 72 if err != nil { 73 log.Printf("[ERROR] dump request failed because %s", err) 74 return err 75 } 76 log.Printf("[DEBUG] http request = %s", outbytes) 77 } 78 httpResponse, err := c.httpClient.Do(httpRequest) 79 if err != nil { 80 msg := fmt.Sprintf("Fail to get response because %s", err) 81 return errors.NewTencentCloudSDKError("ClientError.NetworkError", msg, "") 82 } 83 err = tchttp.ParseFromHttpResponse(httpResponse, response) 84 return err 85} 86 87func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Response) (err error) { 88 headers := map[string]string{ 89 "Host": request.GetDomain(), 90 "X-TC-Action": request.GetAction(), 91 "X-TC-Version": request.GetVersion(), 92 "X-TC-Timestamp": request.GetParams()["Timestamp"], 93 "X-TC-RequestClient": request.GetParams()["RequestClient"], 94 "X-TC-Language": c.profile.Language, 95 } 96 if c.region != "" { 97 headers["X-TC-Region"] = c.region 98 } 99 if c.credential.Token != "" { 100 headers["X-TC-Token"] = c.credential.Token 101 } 102 if request.GetHttpMethod() == "GET" { 103 headers["Content-Type"] = "application/x-www-form-urlencoded" 104 } else { 105 headers["Content-Type"] = "application/json" 106 } 107 108 // start signature v3 process 109 110 // build canonical request string 111 httpRequestMethod := request.GetHttpMethod() 112 canonicalURI := "/" 113 canonicalQueryString := "" 114 if httpRequestMethod == "GET" { 115 err = tchttp.ConstructParams(request) 116 if err != nil { 117 return err 118 } 119 params := make(map[string]string) 120 for key, value := range request.GetParams() { 121 params[key] = value 122 } 123 delete(params, "Action") 124 delete(params, "Version") 125 delete(params, "Nonce") 126 delete(params, "Region") 127 delete(params, "RequestClient") 128 delete(params, "Timestamp") 129 canonicalQueryString = tchttp.GetUrlQueriesEncoded(params) 130 } 131 canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\n", headers["Content-Type"], headers["Host"]) 132 signedHeaders := "content-type;host" 133 requestPayload := "" 134 if httpRequestMethod == "POST" { 135 b, err := json.Marshal(request) 136 if err != nil { 137 return err 138 } 139 requestPayload = string(b) 140 } 141 hashedRequestPayload := "" 142 if c.unsignedPayload { 143 hashedRequestPayload = sha256hex("UNSIGNED-PAYLOAD") 144 headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD" 145 } else { 146 hashedRequestPayload = sha256hex(requestPayload) 147 } 148 canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", 149 httpRequestMethod, 150 canonicalURI, 151 canonicalQueryString, 152 canonicalHeaders, 153 signedHeaders, 154 hashedRequestPayload) 155 //log.Println("canonicalRequest:", canonicalRequest) 156 157 // build string to sign 158 algorithm := "TC3-HMAC-SHA256" 159 requestTimestamp := headers["X-TC-Timestamp"] 160 timestamp, _ := strconv.ParseInt(requestTimestamp, 10, 64) 161 t := time.Unix(timestamp, 0).UTC() 162 // must be the format 2006-01-02, ref to package time for more info 163 date := t.Format("2006-01-02") 164 credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, request.GetService()) 165 hashedCanonicalRequest := sha256hex(canonicalRequest) 166 string2sign := fmt.Sprintf("%s\n%s\n%s\n%s", 167 algorithm, 168 requestTimestamp, 169 credentialScope, 170 hashedCanonicalRequest) 171 //log.Println("string2sign", string2sign) 172 173 // sign string 174 secretDate := hmacsha256(date, "TC3"+c.credential.SecretKey) 175 secretService := hmacsha256(request.GetService(), secretDate) 176 secretKey := hmacsha256("tc3_request", secretService) 177 signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretKey))) 178 //log.Println("signature", signature) 179 180 // build authorization 181 authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", 182 algorithm, 183 c.credential.SecretId, 184 credentialScope, 185 signedHeaders, 186 signature) 187 //log.Println("authorization", authorization) 188 189 headers["Authorization"] = authorization 190 url := "https://" + request.GetDomain() + request.GetPath() 191 if canonicalQueryString != "" { 192 url = url + "?" + canonicalQueryString 193 } 194 httpRequest, err := http.NewRequest(httpRequestMethod, url, strings.NewReader(requestPayload)) 195 if err != nil { 196 return err 197 } 198 for k, v := range headers { 199 httpRequest.Header[k] = []string{v} 200 } 201 if c.debug { 202 outbytes, err := httputil.DumpRequest(httpRequest, true) 203 if err != nil { 204 log.Printf("[ERROR] dump request failed because %s", err) 205 return err 206 } 207 log.Printf("[DEBUG] http request = %s", outbytes) 208 } 209 httpResponse, err := c.httpClient.Do(httpRequest) 210 if err != nil { 211 msg := fmt.Sprintf("Fail to get response because %s", err) 212 return errors.NewTencentCloudSDKError("ClientError.NetworkError", msg, "") 213 } 214 err = tchttp.ParseFromHttpResponse(httpResponse, response) 215 return err 216} 217 218func (c *Client) GetRegion() string { 219 return c.region 220} 221 222func (c *Client) Init(region string) *Client { 223 c.httpClient = &http.Client{} 224 c.region = region 225 c.signMethod = "TC3-HMAC-SHA256" 226 c.debug = false 227 log.SetFlags(log.LstdFlags | log.Lshortfile) 228 return c 229} 230 231func (c *Client) WithSecretId(secretId, secretKey string) *Client { 232 c.credential = NewCredential(secretId, secretKey) 233 return c 234} 235 236func (c *Client) WithCredential(cred *Credential) *Client { 237 c.credential = cred 238 return c 239} 240 241func (c *Client) WithProfile(clientProfile *profile.ClientProfile) *Client { 242 c.profile = clientProfile 243 c.signMethod = clientProfile.SignMethod 244 c.unsignedPayload = clientProfile.UnsignedPayload 245 c.httpProfile = clientProfile.HttpProfile 246 c.httpClient.Timeout = time.Duration(c.httpProfile.ReqTimeout) * time.Second 247 return c 248} 249 250func (c *Client) WithSignatureMethod(method string) *Client { 251 c.signMethod = method 252 return c 253} 254 255func (c *Client) WithHttpTransport(transport http.RoundTripper) *Client { 256 c.httpClient.Transport = transport 257 return c 258} 259 260func NewClientWithSecretId(secretId, secretKey, region string) (client *Client, err error) { 261 client = &Client{} 262 client.Init(region).WithSecretId(secretId, secretKey) 263 return 264} 265