github.com/aliyun/aliyun-oss-go-sdk@v3.0.2+incompatible/oss/conn.go (about) 1 package oss 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/md5" 7 "encoding/base64" 8 "encoding/json" 9 "encoding/xml" 10 "fmt" 11 "hash" 12 "io" 13 "io/ioutil" 14 "net" 15 "net/http" 16 "net/url" 17 "os" 18 "sort" 19 "strconv" 20 "strings" 21 "time" 22 ) 23 24 // Conn defines OSS Conn 25 type Conn struct { 26 config *Config 27 url *urlMaker 28 client *http.Client 29 } 30 31 var signKeyList = []string{"acl", "uploads", "location", "cors", 32 "logging", "website", "referer", "lifecycle", 33 "delete", "append", "tagging", "objectMeta", 34 "uploadId", "partNumber", "security-token", 35 "position", "img", "style", "styleName", 36 "replication", "replicationProgress", 37 "replicationLocation", "cname", "bucketInfo", 38 "comp", "qos", "live", "status", "vod", 39 "startTime", "endTime", "symlink", 40 "x-oss-process", "response-content-type", "x-oss-traffic-limit", 41 "response-content-language", "response-expires", 42 "response-cache-control", "response-content-disposition", 43 "response-content-encoding", "udf", "udfName", "udfImage", 44 "udfId", "udfImageDesc", "udfApplication", "comp", 45 "udfApplicationLog", "restore", "callback", "callback-var", "qosInfo", 46 "policy", "stat", "encryption", "versions", "versioning", "versionId", "requestPayment", 47 "x-oss-request-payer", "sequential", 48 "inventory", "inventoryId", "continuation-token", "asyncFetch", 49 "worm", "wormId", "wormExtend", "withHashContext", 50 "x-oss-enable-md5", "x-oss-enable-sha1", "x-oss-enable-sha256", 51 "x-oss-hash-ctx", "x-oss-md5-ctx", "transferAcceleration", 52 "regionList", "cloudboxes", "x-oss-ac-source-ip", "x-oss-ac-subnet-mask", "x-oss-ac-vpc-id", "x-oss-ac-forward-allow", 53 "metaQuery", "resourceGroup", "rtc", "x-oss-async-process", "responseHeader", 54 } 55 56 const ( 57 timeFormatV4 = "20060102T150405Z" 58 shortTimeFormatV4 = "20060102" 59 signingAlgorithmV4 = "OSS4-HMAC-SHA256" 60 ) 61 62 // init initializes Conn 63 func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error { 64 if client == nil { 65 // New transport 66 transport := newTransport(conn, config) 67 68 // Proxy 69 if conn.config.IsUseProxy { 70 proxyURL, err := url.Parse(config.ProxyHost) 71 if err != nil { 72 return err 73 } 74 if config.IsAuthProxy { 75 if config.ProxyPassword != "" { 76 proxyURL.User = url.UserPassword(config.ProxyUser, config.ProxyPassword) 77 } else { 78 proxyURL.User = url.User(config.ProxyUser) 79 } 80 } 81 transport.Proxy = http.ProxyURL(proxyURL) 82 } 83 client = &http.Client{Transport: transport} 84 if !config.RedirectEnabled { 85 disableHTTPRedirect(client) 86 } 87 } 88 89 conn.config = config 90 conn.url = urlMaker 91 conn.client = client 92 93 return nil 94 } 95 96 // Do sends request and returns the response 97 func (conn Conn) Do(method, bucketName, objectName string, params map[string]interface{}, headers map[string]string, 98 data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { 99 return conn.DoWithContext(nil, method, bucketName, objectName, params, headers, data, initCRC, listener) 100 } 101 102 // DoWithContext sends request and returns the response with context 103 func (conn Conn) DoWithContext(ctx context.Context, method, bucketName, objectName string, params map[string]interface{}, headers map[string]string, 104 data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { 105 urlParams := conn.getURLParams(params) 106 subResource := conn.getSubResource(params) 107 uri := conn.url.getURL(bucketName, objectName, urlParams) 108 109 resource := "" 110 if conn.config.AuthVersion != AuthV4 { 111 resource = conn.getResource(bucketName, objectName, subResource) 112 } else { 113 resource = conn.getResourceV4(bucketName, objectName, subResource) 114 } 115 116 return conn.doRequest(ctx, method, uri, resource, headers, data, initCRC, listener) 117 } 118 119 // DoURL sends the request with signed URL and returns the response result. 120 func (conn Conn) DoURL(method HTTPMethod, signedURL string, headers map[string]string, 121 data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { 122 return conn.DoURLWithContext(nil, method, signedURL, headers, data, initCRC, listener) 123 } 124 125 // DoURLWithContext sends the request with signed URL and context and returns the response result. 126 func (conn Conn) DoURLWithContext(ctx context.Context, method HTTPMethod, signedURL string, headers map[string]string, 127 data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { 128 // Get URI from signedURL 129 uri, err := url.ParseRequestURI(signedURL) 130 if err != nil { 131 return nil, err 132 } 133 134 m := strings.ToUpper(string(method)) 135 req := &http.Request{ 136 Method: m, 137 URL: uri, 138 Proto: "HTTP/1.1", 139 ProtoMajor: 1, 140 ProtoMinor: 1, 141 Header: make(http.Header), 142 Host: uri.Host, 143 } 144 145 if ctx != nil { 146 req = req.WithContext(ctx) 147 } 148 tracker := &readerTracker{completedBytes: 0} 149 fd, crc := conn.handleBody(req, data, initCRC, listener, tracker) 150 if fd != nil { 151 defer func() { 152 fd.Close() 153 os.Remove(fd.Name()) 154 }() 155 } 156 157 if conn.config.IsAuthProxy { 158 auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword 159 basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 160 req.Header.Set("Proxy-Authorization", basic) 161 } 162 163 req.Header.Set(HTTPHeaderHost, req.Host) 164 req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent) 165 166 if headers != nil { 167 for k, v := range headers { 168 req.Header.Set(k, v) 169 } 170 } 171 172 // Transfer started 173 event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0) 174 publishProgress(listener, event) 175 176 if conn.config.LogLevel >= Debug { 177 conn.LoggerHTTPReq(req) 178 } 179 180 resp, err := conn.client.Do(req) 181 if err != nil { 182 // Transfer failed 183 conn.config.WriteLog(Debug, "[Resp:%p]http error:%s\n", req, err.Error()) 184 event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength, 0) 185 publishProgress(listener, event) 186 187 return nil, err 188 } 189 190 if conn.config.LogLevel >= Debug { 191 //print out http resp 192 conn.LoggerHTTPResp(req, resp) 193 } 194 195 // Transfer completed 196 event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength, 0) 197 publishProgress(listener, event) 198 199 return conn.handleResponse(resp, crc) 200 } 201 202 func (conn Conn) getURLParams(params map[string]interface{}) string { 203 // Sort 204 keys := make([]string, 0, len(params)) 205 for k := range params { 206 keys = append(keys, k) 207 } 208 sort.Strings(keys) 209 210 // Serialize 211 var buf bytes.Buffer 212 for _, k := range keys { 213 if buf.Len() > 0 { 214 buf.WriteByte('&') 215 } 216 buf.WriteString(url.QueryEscape(k)) 217 if params[k] != nil && params[k].(string) != "" { 218 buf.WriteString("=" + strings.Replace(url.QueryEscape(params[k].(string)), "+", "%20", -1)) 219 } 220 } 221 222 return buf.String() 223 } 224 225 func (conn Conn) getSubResource(params map[string]interface{}) string { 226 // Sort 227 keys := make([]string, 0, len(params)) 228 signParams := make(map[string]string) 229 for k := range params { 230 if conn.config.AuthVersion == AuthV2 || conn.config.AuthVersion == AuthV4 { 231 encodedKey := url.QueryEscape(k) 232 keys = append(keys, encodedKey) 233 if params[k] != nil && params[k] != "" { 234 signParams[encodedKey] = strings.Replace(url.QueryEscape(params[k].(string)), "+", "%20", -1) 235 } 236 } else if conn.isParamSign(k) { 237 keys = append(keys, k) 238 if params[k] != nil { 239 signParams[k] = params[k].(string) 240 } 241 } 242 } 243 sort.Strings(keys) 244 245 // Serialize 246 var buf bytes.Buffer 247 for _, k := range keys { 248 if buf.Len() > 0 { 249 buf.WriteByte('&') 250 } 251 buf.WriteString(k) 252 if _, ok := signParams[k]; ok { 253 if signParams[k] != "" { 254 buf.WriteString("=" + signParams[k]) 255 } 256 } 257 } 258 return buf.String() 259 } 260 261 func (conn Conn) isParamSign(paramKey string) bool { 262 for _, k := range signKeyList { 263 if paramKey == k { 264 return true 265 } 266 } 267 return false 268 } 269 270 // getResource gets canonicalized resource 271 func (conn Conn) getResource(bucketName, objectName, subResource string) string { 272 if subResource != "" { 273 subResource = "?" + subResource 274 } 275 if bucketName == "" { 276 if conn.config.AuthVersion == AuthV2 { 277 return url.QueryEscape("/") + subResource 278 } 279 return fmt.Sprintf("/%s%s", bucketName, subResource) 280 } 281 if conn.config.AuthVersion == AuthV2 { 282 return url.QueryEscape("/"+bucketName+"/") + strings.Replace(url.QueryEscape(objectName), "+", "%20", -1) + subResource 283 } 284 return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource) 285 } 286 287 // getResource gets canonicalized resource 288 func (conn Conn) getResourceV4(bucketName, objectName, subResource string) string { 289 if subResource != "" { 290 subResource = "?" + subResource 291 } 292 293 if bucketName == "" { 294 return fmt.Sprintf("/%s", subResource) 295 } 296 297 if objectName != "" { 298 objectName = url.QueryEscape(objectName) 299 objectName = strings.Replace(objectName, "+", "%20", -1) 300 objectName = strings.Replace(objectName, "%2F", "/", -1) 301 return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource) 302 } 303 return fmt.Sprintf("/%s/%s", bucketName, subResource) 304 } 305 306 func (conn Conn) doRequest(ctx context.Context, method string, uri *url.URL, canonicalizedResource string, headers map[string]string, 307 data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { 308 method = strings.ToUpper(method) 309 var req *http.Request 310 var err error 311 req = &http.Request{ 312 Method: method, 313 URL: uri, 314 Proto: "HTTP/1.1", 315 ProtoMajor: 1, 316 ProtoMinor: 1, 317 Header: make(http.Header), 318 Host: uri.Host, 319 } 320 if ctx != nil { 321 req = req.WithContext(ctx) 322 } 323 tracker := &readerTracker{completedBytes: 0} 324 fd, crc := conn.handleBody(req, data, initCRC, listener, tracker) 325 if fd != nil { 326 defer func() { 327 fd.Close() 328 os.Remove(fd.Name()) 329 }() 330 } 331 332 if conn.config.IsAuthProxy { 333 auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword 334 basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 335 req.Header.Set("Proxy-Authorization", basic) 336 } 337 338 stNow := time.Now().UTC() 339 req.Header.Set(HTTPHeaderDate, stNow.Format(http.TimeFormat)) 340 req.Header.Set(HTTPHeaderHost, req.Host) 341 req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent) 342 343 if conn.config.AuthVersion == AuthV4 { 344 req.Header.Set(HttpHeaderOssContentSha256, DefaultContentSha256) 345 } 346 347 var akIf Credentials 348 if providerE, ok := conn.config.CredentialsProvider.(CredentialsProviderE); ok { 349 if akIf, err = providerE.GetCredentialsE(); err != nil { 350 return nil, err 351 } 352 } else { 353 akIf = conn.config.GetCredentials() 354 } 355 356 if akIf.GetSecurityToken() != "" { 357 req.Header.Set(HTTPHeaderOssSecurityToken, akIf.GetSecurityToken()) 358 } 359 360 if headers != nil { 361 for k, v := range headers { 362 req.Header.Set(k, v) 363 } 364 } 365 366 conn.signHeader(req, canonicalizedResource, akIf) 367 368 // Transfer started 369 event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0) 370 publishProgress(listener, event) 371 372 if conn.config.LogLevel >= Debug { 373 conn.LoggerHTTPReq(req) 374 } 375 376 resp, err := conn.client.Do(req) 377 378 if err != nil { 379 conn.config.WriteLog(Debug, "[Resp:%p]http error:%s\n", req, err.Error()) 380 // Transfer failed 381 event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength, 0) 382 publishProgress(listener, event) 383 return nil, err 384 } 385 386 if conn.config.LogLevel >= Debug { 387 //print out http resp 388 conn.LoggerHTTPResp(req, resp) 389 } 390 391 // Transfer completed 392 event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength, 0) 393 publishProgress(listener, event) 394 395 return conn.handleResponse(resp, crc) 396 } 397 398 func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expiration int64, params map[string]interface{}, headers map[string]string) (string, error) { 399 var akIf Credentials 400 var err error 401 if providerE, ok := conn.config.CredentialsProvider.(CredentialsProviderE); ok { 402 if akIf, err = providerE.GetCredentialsE(); err != nil { 403 return "", err 404 } 405 } else { 406 akIf = conn.config.GetCredentials() 407 } 408 409 m := strings.ToUpper(string(method)) 410 req := &http.Request{ 411 Method: m, 412 Header: make(http.Header), 413 } 414 415 if conn.config.IsAuthProxy { 416 auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword 417 basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) 418 req.Header.Set("Proxy-Authorization", basic) 419 } 420 421 if conn.config.AuthVersion == AuthV4 { 422 if akIf.GetSecurityToken() != "" { 423 params[HTTPParamOssSecurityToken] = akIf.GetSecurityToken() 424 } 425 426 if headers != nil { 427 for k, v := range headers { 428 req.Header.Set(k, v) 429 } 430 } 431 432 now := time.Now().UTC() 433 expires := expiration - now.Unix() 434 product := conn.config.GetSignProduct() 435 region := conn.config.GetSignRegion() 436 strDay := now.Format(shortTimeFormatV4) 437 additionalList, _ := conn.getAdditionalHeaderKeys(req) 438 439 params[HTTPParamSignatureVersion] = signingAlgorithmV4 440 params[HTTPParamCredential] = fmt.Sprintf("%s/%s/%s/%s/aliyun_v4_request", akIf.GetAccessKeyID(), strDay, region, product) 441 params[HTTPParamDate] = now.Format(timeFormatV4) 442 params[HTTPParamExpiresV2] = strconv.FormatInt(expires, 10) 443 if len(additionalList) > 0 { 444 params[HTTPParamAdditionalHeadersV2] = strings.Join(additionalList, ";") 445 } 446 447 subResource := conn.getSubResource(params) 448 canonicalizedResource := conn.getResourceV4(bucketName, objectName, subResource) 449 authorizationStr := conn.getSignedStrV4(req, canonicalizedResource, akIf.GetAccessKeySecret(), &now) 450 params[HTTPParamSignatureV2] = authorizationStr 451 } else { 452 if akIf.GetSecurityToken() != "" { 453 params[HTTPParamSecurityToken] = akIf.GetSecurityToken() 454 } 455 456 req.Header.Set(HTTPHeaderDate, strconv.FormatInt(expiration, 10)) 457 458 if headers != nil { 459 for k, v := range headers { 460 req.Header.Set(k, v) 461 } 462 } 463 464 if conn.config.AuthVersion == AuthV2 { 465 params[HTTPParamSignatureVersion] = "OSS2" 466 params[HTTPParamExpiresV2] = strconv.FormatInt(expiration, 10) 467 params[HTTPParamAccessKeyIDV2] = conn.config.AccessKeyID 468 additionalList, _ := conn.getAdditionalHeaderKeys(req) 469 if len(additionalList) > 0 { 470 params[HTTPParamAdditionalHeadersV2] = strings.Join(additionalList, ";") 471 } 472 } 473 474 subResource := conn.getSubResource(params) 475 canonicalizedResource := conn.getResource(bucketName, objectName, subResource) 476 signedStr := conn.getSignedStr(req, canonicalizedResource, akIf.GetAccessKeySecret()) 477 478 if conn.config.AuthVersion == AuthV1 { 479 params[HTTPParamExpires] = strconv.FormatInt(expiration, 10) 480 params[HTTPParamAccessKeyID] = akIf.GetAccessKeyID() 481 params[HTTPParamSignature] = signedStr 482 } else if conn.config.AuthVersion == AuthV2 { 483 params[HTTPParamSignatureV2] = signedStr 484 } 485 } 486 487 urlParams := conn.getURLParams(params) 488 return conn.url.getSignURL(bucketName, objectName, urlParams), nil 489 } 490 491 func (conn Conn) signRtmpURL(bucketName, channelName, playlistName string, expiration int64) string { 492 params := map[string]interface{}{} 493 if playlistName != "" { 494 params[HTTPParamPlaylistName] = playlistName 495 } 496 expireStr := strconv.FormatInt(expiration, 10) 497 params[HTTPParamExpires] = expireStr 498 499 akIf := conn.config.GetCredentials() 500 if akIf.GetAccessKeyID() != "" { 501 params[HTTPParamAccessKeyID] = akIf.GetAccessKeyID() 502 if akIf.GetSecurityToken() != "" { 503 params[HTTPParamSecurityToken] = akIf.GetSecurityToken() 504 } 505 signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, akIf.GetAccessKeySecret(), params) 506 params[HTTPParamSignature] = signedStr 507 } 508 509 urlParams := conn.getURLParams(params) 510 return conn.url.getSignRtmpURL(bucketName, channelName, urlParams) 511 } 512 513 // handleBody handles request body 514 func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64, 515 listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) { 516 var file *os.File 517 var crc hash.Hash64 518 reader := body 519 readerLen, err := GetReaderLen(reader) 520 if err == nil { 521 req.ContentLength = readerLen 522 } 523 req.Header.Set(HTTPHeaderContentLength, strconv.FormatInt(req.ContentLength, 10)) 524 525 // MD5 526 if body != nil && conn.config.IsEnableMD5 && req.Header.Get(HTTPHeaderContentMD5) == "" { 527 md5 := "" 528 reader, md5, file, _ = calcMD5(body, req.ContentLength, conn.config.MD5Threshold) 529 req.Header.Set(HTTPHeaderContentMD5, md5) 530 } 531 532 // CRC 533 if reader != nil && conn.config.IsEnableCRC { 534 crc = NewCRC(CrcTable(), initCRC) 535 reader = TeeReader(reader, crc, req.ContentLength, listener, tracker) 536 } 537 538 // HTTP body 539 rc, ok := reader.(io.ReadCloser) 540 if !ok && reader != nil { 541 rc = ioutil.NopCloser(reader) 542 } 543 544 if conn.isUploadLimitReq(req) { 545 limitReader := &LimitSpeedReader{ 546 reader: rc, 547 ossLimiter: conn.config.UploadLimiter, 548 } 549 req.Body = limitReader 550 } else { 551 req.Body = rc 552 } 553 return file, crc 554 } 555 556 // isUploadLimitReq: judge limit upload speed or not 557 func (conn Conn) isUploadLimitReq(req *http.Request) bool { 558 if conn.config.UploadLimitSpeed == 0 || conn.config.UploadLimiter == nil { 559 return false 560 } 561 562 if req.Method != "GET" && req.Method != "DELETE" && req.Method != "HEAD" { 563 if req.ContentLength > 0 { 564 return true 565 } 566 } 567 return false 568 } 569 570 func tryGetFileSize(f *os.File) int64 { 571 fInfo, _ := f.Stat() 572 return fInfo.Size() 573 } 574 575 // handleResponse handles response 576 func (conn Conn) handleResponse(resp *http.Response, crc hash.Hash64) (*Response, error) { 577 var cliCRC uint64 578 var srvCRC uint64 579 580 statusCode := resp.StatusCode 581 if statusCode/100 != 2 { 582 if statusCode >= 400 && statusCode <= 505 { 583 // 4xx and 5xx indicate that the operation has error occurred 584 var respBody []byte 585 var errorXml []byte 586 respBody, err := readResponseBody(resp) 587 if err != nil { 588 return nil, err 589 } 590 errorXml = respBody 591 if len(respBody) == 0 && len(resp.Header.Get(HTTPHeaderOssErr)) > 0 { 592 errorXml, err = base64.StdEncoding.DecodeString(resp.Header.Get(HTTPHeaderOssErr)) 593 if err != nil { 594 errorXml = respBody 595 } 596 } 597 if len(errorXml) == 0 { 598 err = ServiceError{ 599 StatusCode: statusCode, 600 RequestID: resp.Header.Get(HTTPHeaderOssRequestID), 601 Ec: resp.Header.Get(HTTPHeaderOssEc), 602 } 603 } else { 604 srvErr, errIn := serviceErrFromXML(errorXml, resp.StatusCode, 605 resp.Header.Get(HTTPHeaderOssRequestID)) 606 if errIn != nil { // error unmarshal the error response 607 if len(resp.Header.Get(HTTPHeaderOssEc)) > 0 { 608 err = fmt.Errorf("oss: service returned invalid response body, status = %s, RequestId = %s, ec = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID), resp.Header.Get(HTTPHeaderOssEc)) 609 } else { 610 err = fmt.Errorf("oss: service returned invalid response body, status = %s, RequestId = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID)) 611 } 612 } else { 613 err = srvErr 614 } 615 } 616 return &Response{ 617 StatusCode: resp.StatusCode, 618 Headers: resp.Header, 619 Body: ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body 620 }, err 621 } else if statusCode >= 300 && statusCode <= 307 { 622 // OSS use 3xx, but response has no body 623 err := fmt.Errorf("oss: service returned %d,%s", resp.StatusCode, resp.Status) 624 return &Response{ 625 StatusCode: resp.StatusCode, 626 Headers: resp.Header, 627 Body: resp.Body, 628 }, err 629 } else { 630 // (0,300) [308,400) [506,) 631 // Other extended http StatusCode 632 var respBody []byte 633 var errorXml []byte 634 respBody, err := readResponseBody(resp) 635 if err != nil { 636 return &Response{StatusCode: resp.StatusCode, Headers: resp.Header, Body: ioutil.NopCloser(bytes.NewReader(respBody))}, err 637 } 638 errorXml = respBody 639 if len(respBody) == 0 && len(resp.Header.Get(HTTPHeaderOssErr)) > 0 { 640 errorXml, err = base64.StdEncoding.DecodeString(resp.Header.Get(HTTPHeaderOssErr)) 641 if err != nil { 642 errorXml = respBody 643 } 644 } 645 if len(errorXml) == 0 { 646 err = ServiceError{ 647 StatusCode: statusCode, 648 RequestID: resp.Header.Get(HTTPHeaderOssRequestID), 649 Ec: resp.Header.Get(HTTPHeaderOssEc), 650 } 651 } else { 652 srvErr, errIn := serviceErrFromXML(errorXml, resp.StatusCode, 653 resp.Header.Get(HTTPHeaderOssRequestID)) 654 if errIn != nil { // error unmarshal the error response 655 if len(resp.Header.Get(HTTPHeaderOssEc)) > 0 { 656 err = fmt.Errorf("unknown response body, status = %s, RequestId = %s, ec = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID), resp.Header.Get(HTTPHeaderOssEc)) 657 } else { 658 err = fmt.Errorf("unknown response body, status = %s, RequestId = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID)) 659 } 660 } else { 661 err = srvErr 662 } 663 } 664 return &Response{ 665 StatusCode: resp.StatusCode, 666 Headers: resp.Header, 667 Body: ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body 668 }, err 669 } 670 } else { 671 if conn.config.IsEnableCRC && crc != nil { 672 cliCRC = crc.Sum64() 673 } 674 srvCRC, _ = strconv.ParseUint(resp.Header.Get(HTTPHeaderOssCRC64), 10, 64) 675 676 realBody := resp.Body 677 if conn.isDownloadLimitResponse(resp) { 678 limitReader := &LimitSpeedReader{ 679 reader: realBody, 680 ossLimiter: conn.config.DownloadLimiter, 681 } 682 realBody = limitReader 683 } 684 685 // 2xx, successful 686 return &Response{ 687 StatusCode: resp.StatusCode, 688 Headers: resp.Header, 689 Body: realBody, 690 ClientCRC: cliCRC, 691 ServerCRC: srvCRC, 692 }, nil 693 } 694 } 695 696 // isUploadLimitReq: judge limit upload speed or not 697 func (conn Conn) isDownloadLimitResponse(resp *http.Response) bool { 698 if resp == nil || conn.config.DownloadLimitSpeed == 0 || conn.config.DownloadLimiter == nil { 699 return false 700 } 701 702 if strings.EqualFold(resp.Request.Method, "GET") { 703 return true 704 } 705 return false 706 } 707 708 // LoggerHTTPReq Print the header information of the http request 709 func (conn Conn) LoggerHTTPReq(req *http.Request) { 710 var logBuffer bytes.Buffer 711 logBuffer.WriteString(fmt.Sprintf("[Req:%p]Method:%s\t", req, req.Method)) 712 logBuffer.WriteString(fmt.Sprintf("Host:%s\t", req.URL.Host)) 713 logBuffer.WriteString(fmt.Sprintf("Path:%s\t", req.URL.Path)) 714 logBuffer.WriteString(fmt.Sprintf("Query:%s\t", req.URL.RawQuery)) 715 logBuffer.WriteString(fmt.Sprintf("Header info:")) 716 717 for k, v := range req.Header { 718 var valueBuffer bytes.Buffer 719 for j := 0; j < len(v); j++ { 720 if j > 0 { 721 valueBuffer.WriteString(" ") 722 } 723 valueBuffer.WriteString(v[j]) 724 } 725 logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String())) 726 } 727 conn.config.WriteLog(Debug, "%s\n", logBuffer.String()) 728 } 729 730 // LoggerHTTPResp Print Response to http request 731 func (conn Conn) LoggerHTTPResp(req *http.Request, resp *http.Response) { 732 var logBuffer bytes.Buffer 733 logBuffer.WriteString(fmt.Sprintf("[Resp:%p]StatusCode:%d\t", req, resp.StatusCode)) 734 logBuffer.WriteString(fmt.Sprintf("Header info:")) 735 for k, v := range resp.Header { 736 var valueBuffer bytes.Buffer 737 for j := 0; j < len(v); j++ { 738 if j > 0 { 739 valueBuffer.WriteString(" ") 740 } 741 valueBuffer.WriteString(v[j]) 742 } 743 logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String())) 744 } 745 conn.config.WriteLog(Debug, "%s\n", logBuffer.String()) 746 } 747 748 func calcMD5(body io.Reader, contentLen, md5Threshold int64) (reader io.Reader, b64 string, tempFile *os.File, err error) { 749 if contentLen == 0 || contentLen > md5Threshold { 750 // Huge body, use temporary file 751 tempFile, err = ioutil.TempFile(os.TempDir(), TempFilePrefix) 752 if tempFile != nil { 753 io.Copy(tempFile, body) 754 tempFile.Seek(0, os.SEEK_SET) 755 md5 := md5.New() 756 io.Copy(md5, tempFile) 757 sum := md5.Sum(nil) 758 b64 = base64.StdEncoding.EncodeToString(sum[:]) 759 tempFile.Seek(0, os.SEEK_SET) 760 reader = tempFile 761 } 762 } else { 763 // Small body, use memory 764 buf, _ := ioutil.ReadAll(body) 765 sum := md5.Sum(buf) 766 b64 = base64.StdEncoding.EncodeToString(sum[:]) 767 reader = bytes.NewReader(buf) 768 } 769 return 770 } 771 772 func readResponseBody(resp *http.Response) ([]byte, error) { 773 defer resp.Body.Close() 774 out, err := ioutil.ReadAll(resp.Body) 775 if err == io.EOF { 776 err = nil 777 } 778 return out, err 779 } 780 781 func serviceErrFromXML(body []byte, statusCode int, requestID string) (ServiceError, error) { 782 var storageErr ServiceError 783 784 if err := xml.Unmarshal(body, &storageErr); err != nil { 785 return storageErr, err 786 } 787 788 storageErr.StatusCode = statusCode 789 storageErr.RequestID = requestID 790 storageErr.RawMessage = string(body) 791 return storageErr, nil 792 } 793 794 func xmlUnmarshal(body io.Reader, v interface{}) error { 795 data, err := ioutil.ReadAll(body) 796 if err != nil { 797 return err 798 } 799 return xml.Unmarshal(data, v) 800 } 801 802 func jsonUnmarshal(body io.Reader, v interface{}) error { 803 data, err := ioutil.ReadAll(body) 804 if err != nil { 805 return err 806 } 807 return json.Unmarshal(data, v) 808 } 809 810 // timeoutConn handles HTTP timeout 811 type timeoutConn struct { 812 conn net.Conn 813 timeout time.Duration 814 longTimeout time.Duration 815 } 816 817 func newTimeoutConn(conn net.Conn, timeout time.Duration, longTimeout time.Duration) *timeoutConn { 818 conn.SetReadDeadline(time.Now().Add(longTimeout)) 819 return &timeoutConn{ 820 conn: conn, 821 timeout: timeout, 822 longTimeout: longTimeout, 823 } 824 } 825 826 func (c *timeoutConn) Read(b []byte) (n int, err error) { 827 c.SetReadDeadline(time.Now().Add(c.timeout)) 828 n, err = c.conn.Read(b) 829 c.SetReadDeadline(time.Now().Add(c.longTimeout)) 830 return n, err 831 } 832 833 func (c *timeoutConn) Write(b []byte) (n int, err error) { 834 c.SetWriteDeadline(time.Now().Add(c.timeout)) 835 n, err = c.conn.Write(b) 836 c.SetReadDeadline(time.Now().Add(c.longTimeout)) 837 return n, err 838 } 839 840 func (c *timeoutConn) Close() error { 841 return c.conn.Close() 842 } 843 844 func (c *timeoutConn) LocalAddr() net.Addr { 845 return c.conn.LocalAddr() 846 } 847 848 func (c *timeoutConn) RemoteAddr() net.Addr { 849 return c.conn.RemoteAddr() 850 } 851 852 func (c *timeoutConn) SetDeadline(t time.Time) error { 853 return c.conn.SetDeadline(t) 854 } 855 856 func (c *timeoutConn) SetReadDeadline(t time.Time) error { 857 return c.conn.SetReadDeadline(t) 858 } 859 860 func (c *timeoutConn) SetWriteDeadline(t time.Time) error { 861 return c.conn.SetWriteDeadline(t) 862 } 863 864 // UrlMaker builds URL and resource 865 const ( 866 urlTypeCname = 1 867 urlTypeIP = 2 868 urlTypeAliyun = 3 869 urlTypePathStyle = 4 870 ) 871 872 type urlMaker struct { 873 Scheme string // HTTP or HTTPS 874 NetLoc string // Host or IP 875 Type int // 1 CNAME, 2 IP, 3 ALIYUN 876 IsProxy bool // Proxy 877 } 878 879 // Init parses endpoint 880 func (um *urlMaker) Init(endpoint string, isCname bool, isProxy bool) error { 881 return um.InitExt(endpoint, isCname, isProxy, false) 882 } 883 884 // InitExt parses endpoint 885 func (um *urlMaker) InitExt(endpoint string, isCname bool, isProxy bool, isPathStyle bool) error { 886 if strings.HasPrefix(endpoint, "http://") { 887 um.Scheme = "http" 888 um.NetLoc = endpoint[len("http://"):] 889 } else if strings.HasPrefix(endpoint, "https://") { 890 um.Scheme = "https" 891 um.NetLoc = endpoint[len("https://"):] 892 } else { 893 um.Scheme = "http" 894 um.NetLoc = endpoint 895 } 896 897 //use url.Parse() to get real host 898 strUrl := um.Scheme + "://" + um.NetLoc 899 url, err := url.Parse(strUrl) 900 if err != nil { 901 return err 902 } 903 904 um.NetLoc = url.Host 905 host, _, err := net.SplitHostPort(um.NetLoc) 906 if err != nil { 907 host = um.NetLoc 908 if len(host) > 0 && host[0] == '[' && host[len(host)-1] == ']' { 909 host = host[1 : len(host)-1] 910 } 911 } 912 913 ip := net.ParseIP(host) 914 if ip != nil { 915 um.Type = urlTypeIP 916 } else if isCname { 917 um.Type = urlTypeCname 918 } else if isPathStyle { 919 um.Type = urlTypePathStyle 920 } else { 921 um.Type = urlTypeAliyun 922 } 923 um.IsProxy = isProxy 924 925 return nil 926 } 927 928 // getURL gets URL 929 func (um urlMaker) getURL(bucket, object, params string) *url.URL { 930 host, path := um.buildURL(bucket, object) 931 addr := "" 932 if params == "" { 933 addr = fmt.Sprintf("%s://%s%s", um.Scheme, host, path) 934 } else { 935 addr = fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params) 936 } 937 uri, _ := url.ParseRequestURI(addr) 938 return uri 939 } 940 941 // getSignURL gets sign URL 942 func (um urlMaker) getSignURL(bucket, object, params string) string { 943 host, path := um.buildURL(bucket, object) 944 return fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params) 945 } 946 947 // getSignRtmpURL Build Sign Rtmp URL 948 func (um urlMaker) getSignRtmpURL(bucket, channelName, params string) string { 949 host, path := um.buildURL(bucket, "live") 950 951 channelName = url.QueryEscape(channelName) 952 channelName = strings.Replace(channelName, "+", "%20", -1) 953 954 return fmt.Sprintf("rtmp://%s%s/%s?%s", host, path, channelName, params) 955 } 956 957 // buildURL builds URL 958 func (um urlMaker) buildURL(bucket, object string) (string, string) { 959 var host = "" 960 var path = "" 961 962 object = url.QueryEscape(object) 963 object = strings.Replace(object, "+", "%20", -1) 964 965 if um.Type == urlTypeCname { 966 host = um.NetLoc 967 path = "/" + object 968 } else if um.Type == urlTypeIP || um.Type == urlTypePathStyle { 969 if bucket == "" { 970 host = um.NetLoc 971 path = "/" 972 } else { 973 host = um.NetLoc 974 path = fmt.Sprintf("/%s/%s", bucket, object) 975 } 976 } else { 977 if bucket == "" { 978 host = um.NetLoc 979 path = "/" 980 } else { 981 host = bucket + "." + um.NetLoc 982 path = "/" + object 983 } 984 } 985 986 return host, path 987 } 988 989 // buildURL builds URL 990 func (um urlMaker) buildURLV4(bucket, object string) (string, string) { 991 var host = "" 992 var path = "" 993 994 object = url.QueryEscape(object) 995 object = strings.Replace(object, "+", "%20", -1) 996 997 // no escape / 998 object = strings.Replace(object, "%2F", "/", -1) 999 1000 if um.Type == urlTypeCname { 1001 host = um.NetLoc 1002 path = "/" + object 1003 } else if um.Type == urlTypeIP || um.Type == urlTypePathStyle { 1004 if bucket == "" { 1005 host = um.NetLoc 1006 path = "/" 1007 } else { 1008 host = um.NetLoc 1009 path = fmt.Sprintf("/%s/%s", bucket, object) 1010 } 1011 } else { 1012 if bucket == "" { 1013 host = um.NetLoc 1014 path = "/" 1015 } else { 1016 host = bucket + "." + um.NetLoc 1017 path = fmt.Sprintf("/%s/%s", bucket, object) 1018 } 1019 } 1020 return host, path 1021 }