1package common 2 3import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "net/http" 11 "net/url" 12 "os" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/denverdino/aliyungo/util" 18) 19 20// RemovalPolicy.N add index to array item 21// RemovalPolicy=["a", "b"] => RemovalPolicy.1="a" RemovalPolicy.2="b" 22type FlattenArray []string 23 24// string contains underline which will be replaced with dot 25// SystemDisk_Category => SystemDisk.Category 26type UnderlineString string 27 28// A Client represents a client of ECS services 29type Client struct { 30 AccessKeyId string //Access Key Id 31 AccessKeySecret string //Access Key Secret 32 securityToken string 33 debug bool 34 httpClient *http.Client 35 endpoint string 36 version string 37 serviceCode string 38 regionID Region 39 businessInfo string 40 userAgent string 41} 42 43// Initialize properties of a client instance 44func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) { 45 client.AccessKeyId = accessKeyId 46 ak := accessKeySecret 47 if !strings.HasSuffix(ak, "&") { 48 ak += "&" 49 } 50 client.AccessKeySecret = ak 51 client.debug = false 52 handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout")) 53 if err != nil { 54 handshakeTimeout = 0 55 } 56 if handshakeTimeout == 0 { 57 client.httpClient = &http.Client{} 58 } else { 59 t := &http.Transport{ 60 TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second} 61 client.httpClient = &http.Client{Transport: t} 62 } 63 client.endpoint = endpoint 64 client.version = version 65} 66 67// Initialize properties of a client instance including regionID 68func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region) { 69 client.Init(endpoint, version, accessKeyId, accessKeySecret) 70 client.serviceCode = serviceCode 71 client.regionID = regionID 72} 73 74// Intialize client object when all properties are ready 75func (client *Client) InitClient() *Client { 76 client.debug = false 77 handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout")) 78 if err != nil { 79 handshakeTimeout = 0 80 } 81 if handshakeTimeout == 0 { 82 client.httpClient = &http.Client{} 83 } else { 84 t := &http.Transport{ 85 TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second} 86 client.httpClient = &http.Client{Transport: t} 87 } 88 return client 89} 90 91func (client *Client) NewInitForAssumeRole(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region, securityToken string) { 92 client.NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode, regionID) 93 client.securityToken = securityToken 94} 95 96//getLocationEndpoint 97func (client *Client) getEndpointByLocation() string { 98 locationClient := NewLocationClient(client.AccessKeyId, client.AccessKeySecret, client.securityToken) 99 locationClient.SetDebug(true) 100 return locationClient.DescribeOpenAPIEndpoint(client.regionID, client.serviceCode) 101} 102 103//NewClient using location service 104func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret, securityToken string) { 105 locationClient := NewLocationClient(accessKeyId, accessKeySecret, securityToken) 106 locationClient.SetDebug(true) 107 ep := locationClient.DescribeOpenAPIEndpoint(region, serviceCode) 108 if ep == "" { 109 ep = loadEndpointFromFile(region, serviceCode) 110 } 111 112 if ep != "" { 113 client.endpoint = ep 114 } 115} 116 117// Ensure all necessary properties are valid 118func (client *Client) ensureProperties() error { 119 var msg string 120 121 if client.endpoint == "" { 122 msg = fmt.Sprintf("endpoint cannot be empty!") 123 } else if client.version == "" { 124 msg = fmt.Sprintf("version cannot be empty!") 125 } else if client.AccessKeyId == "" { 126 msg = fmt.Sprintf("AccessKeyId cannot be empty!") 127 } else if client.AccessKeySecret == "" { 128 msg = fmt.Sprintf("AccessKeySecret cannot be empty!") 129 } 130 131 if msg != "" { 132 return errors.New(msg) 133 } 134 135 return nil 136} 137 138// ---------------------------------------------------- 139// WithXXX methods 140// ---------------------------------------------------- 141 142// WithEndpoint sets custom endpoint 143func (client *Client) WithEndpoint(endpoint string) *Client { 144 client.SetEndpoint(endpoint) 145 return client 146} 147 148// WithVersion sets custom version 149func (client *Client) WithVersion(version string) *Client { 150 client.SetVersion(version) 151 return client 152} 153 154// WithRegionID sets Region ID 155func (client *Client) WithRegionID(regionID Region) *Client { 156 client.SetRegionID(regionID) 157 return client 158} 159 160//WithServiceCode sets serviceCode 161func (client *Client) WithServiceCode(serviceCode string) *Client { 162 client.SetServiceCode(serviceCode) 163 return client 164} 165 166// WithAccessKeyId sets new AccessKeyId 167func (client *Client) WithAccessKeyId(id string) *Client { 168 client.SetAccessKeyId(id) 169 return client 170} 171 172// WithAccessKeySecret sets new AccessKeySecret 173func (client *Client) WithAccessKeySecret(secret string) *Client { 174 client.SetAccessKeySecret(secret) 175 return client 176} 177 178// WithSecurityToken sets securityToken 179func (client *Client) WithSecurityToken(securityToken string) *Client { 180 client.SetSecurityToken(securityToken) 181 return client 182} 183 184// WithDebug sets debug mode to log the request/response message 185func (client *Client) WithDebug(debug bool) *Client { 186 client.SetDebug(debug) 187 return client 188} 189 190// WithBusinessInfo sets business info to log the request/response message 191func (client *Client) WithBusinessInfo(businessInfo string) *Client { 192 client.SetBusinessInfo(businessInfo) 193 return client 194} 195 196// WithUserAgent sets user agent to the request/response message 197func (client *Client) WithUserAgent(userAgent string) *Client { 198 client.SetUserAgent(userAgent) 199 return client 200} 201 202// ---------------------------------------------------- 203// SetXXX methods 204// ---------------------------------------------------- 205 206// SetEndpoint sets custom endpoint 207func (client *Client) SetEndpoint(endpoint string) { 208 client.endpoint = endpoint 209} 210 211// SetEndpoint sets custom version 212func (client *Client) SetVersion(version string) { 213 client.version = version 214} 215 216// SetEndpoint sets Region ID 217func (client *Client) SetRegionID(regionID Region) { 218 client.regionID = regionID 219} 220 221//SetServiceCode sets serviceCode 222func (client *Client) SetServiceCode(serviceCode string) { 223 client.serviceCode = serviceCode 224} 225 226// SetAccessKeyId sets new AccessKeyId 227func (client *Client) SetAccessKeyId(id string) { 228 client.AccessKeyId = id 229} 230 231// SetAccessKeySecret sets new AccessKeySecret 232func (client *Client) SetAccessKeySecret(secret string) { 233 client.AccessKeySecret = secret + "&" 234} 235 236// SetDebug sets debug mode to log the request/response message 237func (client *Client) SetDebug(debug bool) { 238 client.debug = debug 239} 240 241// SetBusinessInfo sets business info to log the request/response message 242func (client *Client) SetBusinessInfo(businessInfo string) { 243 if strings.HasPrefix(businessInfo, "/") { 244 client.businessInfo = businessInfo 245 } else if businessInfo != "" { 246 client.businessInfo = "/" + businessInfo 247 } 248} 249 250// SetUserAgent sets user agent to the request/response message 251func (client *Client) SetUserAgent(userAgent string) { 252 client.userAgent = userAgent 253} 254 255//set SecurityToken 256func (client *Client) SetSecurityToken(securityToken string) { 257 client.securityToken = securityToken 258} 259 260func (client *Client) initEndpoint() error { 261 // if set any value to "CUSTOMIZED_ENDPOINT" could skip location service. 262 // example: export CUSTOMIZED_ENDPOINT=true 263 if os.Getenv("CUSTOMIZED_ENDPOINT") != "" { 264 return nil 265 } 266 267 if client.serviceCode != "" && client.regionID != "" { 268 endpoint := client.getEndpointByLocation() 269 if endpoint == "" { 270 return GetCustomError("InvalidEndpoint", "endpoint is empty,pls check") 271 } 272 client.endpoint = endpoint 273 } 274 return nil 275} 276 277// Invoke sends the raw HTTP request for ECS services 278func (client *Client) Invoke(action string, args interface{}, response interface{}) error { 279 if err := client.ensureProperties(); err != nil { 280 return err 281 } 282 283 //init endpoint 284 if err := client.initEndpoint(); err != nil { 285 return err 286 } 287 288 request := Request{} 289 request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) 290 291 query := util.ConvertToQueryValues(request) 292 util.SetQueryValues(args, &query) 293 294 // Sign request 295 signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret) 296 297 // Generate the request URL 298 requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature) 299 300 httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil) 301 302 if err != nil { 303 return GetClientError(err) 304 } 305 306 // TODO move to util and add build val flag 307 httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) 308 309 httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent) 310 311 t0 := time.Now() 312 httpResp, err := client.httpClient.Do(httpReq) 313 t1 := time.Now() 314 if err != nil { 315 return GetClientError(err) 316 } 317 statusCode := httpResp.StatusCode 318 319 if client.debug { 320 log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0)) 321 } 322 323 defer httpResp.Body.Close() 324 body, err := ioutil.ReadAll(httpResp.Body) 325 326 if err != nil { 327 return GetClientError(err) 328 } 329 330 if client.debug { 331 var prettyJSON bytes.Buffer 332 err = json.Indent(&prettyJSON, body, "", " ") 333 log.Println(string(prettyJSON.Bytes())) 334 } 335 336 if statusCode >= 400 && statusCode <= 599 { 337 errorResponse := ErrorResponse{} 338 err = json.Unmarshal(body, &errorResponse) 339 ecsError := &Error{ 340 ErrorResponse: errorResponse, 341 StatusCode: statusCode, 342 } 343 return ecsError 344 } 345 346 err = json.Unmarshal(body, response) 347 //log.Printf("%++v", response) 348 if err != nil { 349 return GetClientError(err) 350 } 351 352 return nil 353} 354 355// Invoke sends the raw HTTP request for ECS services 356func (client *Client) InvokeByFlattenMethod(action string, args interface{}, response interface{}) error { 357 if err := client.ensureProperties(); err != nil { 358 return err 359 } 360 361 //init endpoint 362 if err := client.initEndpoint(); err != nil { 363 return err 364 } 365 366 request := Request{} 367 request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) 368 369 query := util.ConvertToQueryValues(request) 370 371 util.SetQueryValueByFlattenMethod(args, &query) 372 373 // Sign request 374 signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret) 375 376 // Generate the request URL 377 requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature) 378 379 httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil) 380 381 if err != nil { 382 return GetClientError(err) 383 } 384 385 // TODO move to util and add build val flag 386 httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) 387 388 httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent) 389 390 t0 := time.Now() 391 httpResp, err := client.httpClient.Do(httpReq) 392 t1 := time.Now() 393 if err != nil { 394 return GetClientError(err) 395 } 396 statusCode := httpResp.StatusCode 397 398 if client.debug { 399 log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0)) 400 } 401 402 defer httpResp.Body.Close() 403 body, err := ioutil.ReadAll(httpResp.Body) 404 405 if err != nil { 406 return GetClientError(err) 407 } 408 409 if client.debug { 410 var prettyJSON bytes.Buffer 411 err = json.Indent(&prettyJSON, body, "", " ") 412 log.Println(string(prettyJSON.Bytes())) 413 } 414 415 if statusCode >= 400 && statusCode <= 599 { 416 errorResponse := ErrorResponse{} 417 err = json.Unmarshal(body, &errorResponse) 418 ecsError := &Error{ 419 ErrorResponse: errorResponse, 420 StatusCode: statusCode, 421 } 422 return ecsError 423 } 424 425 err = json.Unmarshal(body, response) 426 //log.Printf("%++v", response) 427 if err != nil { 428 return GetClientError(err) 429 } 430 431 return nil 432} 433 434// Invoke sends the raw HTTP request for ECS services 435//改进了一下上面那个方法,可以使用各种Http方法 436//2017.1.30 增加了一个path参数,用来拓展访问的地址 437func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error { 438 if err := client.ensureProperties(); err != nil { 439 return err 440 } 441 442 //init endpoint 443 if err := client.initEndpoint(); err != nil { 444 return err 445 } 446 447 request := Request{} 448 request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) 449 data := util.ConvertToQueryValues(request) 450 util.SetQueryValues(args, &data) 451 452 // Sign request 453 signature := util.CreateSignatureForRequest(method, &data, client.AccessKeySecret) 454 455 data.Add("Signature", signature) 456 // Generate the request URL 457 var ( 458 httpReq *http.Request 459 err error 460 ) 461 if method == http.MethodGet { 462 requestURL := client.endpoint + path + "?" + data.Encode() 463 //fmt.Println(requestURL) 464 httpReq, err = http.NewRequest(method, requestURL, nil) 465 } else { 466 //fmt.Println(client.endpoint + path) 467 httpReq, err = http.NewRequest(method, client.endpoint+path, strings.NewReader(data.Encode())) 468 httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") 469 } 470 471 if err != nil { 472 return GetClientError(err) 473 } 474 475 // TODO move to util and add build val flag 476 httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) 477 httpReq.Header.Set("User-Agent", httpReq.Header.Get("User-Agent")+" "+client.userAgent) 478 479 t0 := time.Now() 480 httpResp, err := client.httpClient.Do(httpReq) 481 t1 := time.Now() 482 if err != nil { 483 return GetClientError(err) 484 } 485 statusCode := httpResp.StatusCode 486 487 if client.debug { 488 log.Printf("Invoke %s %s %d (%v) %v", ECSRequestMethod, client.endpoint, statusCode, t1.Sub(t0), data.Encode()) 489 } 490 491 defer httpResp.Body.Close() 492 body, err := ioutil.ReadAll(httpResp.Body) 493 494 if err != nil { 495 return GetClientError(err) 496 } 497 498 if client.debug { 499 var prettyJSON bytes.Buffer 500 err = json.Indent(&prettyJSON, body, "", " ") 501 log.Println(string(prettyJSON.Bytes())) 502 } 503 504 if statusCode >= 400 && statusCode <= 599 { 505 errorResponse := ErrorResponse{} 506 err = json.Unmarshal(body, &errorResponse) 507 ecsError := &Error{ 508 ErrorResponse: errorResponse, 509 StatusCode: statusCode, 510 } 511 return ecsError 512 } 513 514 err = json.Unmarshal(body, response) 515 //log.Printf("%++v", response) 516 if err != nil { 517 return GetClientError(err) 518 } 519 520 return nil 521} 522 523// GenerateClientToken generates the Client Token with random string 524func (client *Client) GenerateClientToken() string { 525 return util.CreateRandomString() 526} 527 528func GetClientErrorFromString(str string) error { 529 return &Error{ 530 ErrorResponse: ErrorResponse{ 531 Code: "AliyunGoClientFailure", 532 Message: str, 533 }, 534 StatusCode: -1, 535 } 536} 537 538func GetClientError(err error) error { 539 return GetClientErrorFromString(err.Error()) 540} 541 542func GetCustomError(code, message string) error { 543 return &Error{ 544 ErrorResponse: ErrorResponse{ 545 Code: code, 546 Message: message, 547 }, 548 StatusCode: 400, 549 } 550} 551