github.com/aavshr/aws-sdk-go@v1.41.3/aws/request/request.go (about) 1 package request 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "reflect" 10 "strings" 11 "time" 12 13 "github.com/aavshr/aws-sdk-go/aws" 14 "github.com/aavshr/aws-sdk-go/aws/awserr" 15 "github.com/aavshr/aws-sdk-go/aws/client/metadata" 16 "github.com/aavshr/aws-sdk-go/internal/sdkio" 17 ) 18 19 const ( 20 // ErrCodeSerialization is the serialization error code that is received 21 // during protocol unmarshaling. 22 ErrCodeSerialization = "SerializationError" 23 24 // ErrCodeRead is an error that is returned during HTTP reads. 25 ErrCodeRead = "ReadError" 26 27 // ErrCodeResponseTimeout is the connection timeout error that is received 28 // during body reads. 29 ErrCodeResponseTimeout = "ResponseTimeout" 30 31 // ErrCodeInvalidPresignExpire is returned when the expire time provided to 32 // presign is invalid 33 ErrCodeInvalidPresignExpire = "InvalidPresignExpireError" 34 35 // CanceledErrorCode is the error code that will be returned by an 36 // API request that was canceled. Requests given a aws.Context may 37 // return this error when canceled. 38 CanceledErrorCode = "RequestCanceled" 39 40 // ErrCodeRequestError is an error preventing the SDK from continuing to 41 // process the request. 42 ErrCodeRequestError = "RequestError" 43 ) 44 45 // A Request is the service request to be made. 46 type Request struct { 47 Config aws.Config 48 ClientInfo metadata.ClientInfo 49 Handlers Handlers 50 51 Retryer 52 AttemptTime time.Time 53 Time time.Time 54 Operation *Operation 55 HTTPRequest *http.Request 56 HTTPResponse *http.Response 57 Body io.ReadSeeker 58 streamingBody io.ReadCloser 59 BodyStart int64 // offset from beginning of Body that the request body starts 60 Params interface{} 61 Error error 62 Data interface{} 63 RequestID string 64 RetryCount int 65 Retryable *bool 66 RetryDelay time.Duration 67 NotHoist bool 68 SignedHeaderVals http.Header 69 LastSignedAt time.Time 70 DisableFollowRedirects bool 71 72 // Additional API error codes that should be retried. IsErrorRetryable 73 // will consider these codes in addition to its built in cases. 74 RetryErrorCodes []string 75 76 // Additional API error codes that should be retried with throttle backoff 77 // delay. IsErrorThrottle will consider these codes in addition to its 78 // built in cases. 79 ThrottleErrorCodes []string 80 81 // A value greater than 0 instructs the request to be signed as Presigned URL 82 // You should not set this field directly. Instead use Request's 83 // Presign or PresignRequest methods. 84 ExpireTime time.Duration 85 86 context aws.Context 87 88 built bool 89 90 // Need to persist an intermediate body between the input Body and HTTP 91 // request body because the HTTP Client's transport can maintain a reference 92 // to the HTTP request's body after the client has returned. This value is 93 // safe to use concurrently and wrap the input Body for each HTTP request. 94 safeBody *offsetReader 95 } 96 97 // An Operation is the service API operation to be made. 98 type Operation struct { 99 Name string 100 HTTPMethod string 101 HTTPPath string 102 *Paginator 103 104 BeforePresignFn func(r *Request) error 105 } 106 107 // New returns a new Request pointer for the service API operation and 108 // parameters. 109 // 110 // A Retryer should be provided to direct how the request is retried. If 111 // Retryer is nil, a default no retry value will be used. You can use 112 // NoOpRetryer in the Client package to disable retry behavior directly. 113 // 114 // Params is any value of input parameters to be the request payload. 115 // Data is pointer value to an object which the request's response 116 // payload will be deserialized to. 117 func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers, 118 retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request { 119 120 if retryer == nil { 121 retryer = noOpRetryer{} 122 } 123 124 method := operation.HTTPMethod 125 if method == "" { 126 method = "POST" 127 } 128 129 httpReq, _ := http.NewRequest(method, "", nil) 130 131 var err error 132 httpReq.URL, err = url.Parse(clientInfo.Endpoint) 133 if err != nil { 134 httpReq.URL = &url.URL{} 135 err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err) 136 } 137 138 if len(operation.HTTPPath) != 0 { 139 opHTTPPath := operation.HTTPPath 140 var opQueryString string 141 if idx := strings.Index(opHTTPPath, "?"); idx >= 0 { 142 opQueryString = opHTTPPath[idx+1:] 143 opHTTPPath = opHTTPPath[:idx] 144 } 145 146 if strings.HasSuffix(httpReq.URL.Path, "/") && strings.HasPrefix(opHTTPPath, "/") { 147 opHTTPPath = opHTTPPath[1:] 148 } 149 httpReq.URL.Path += opHTTPPath 150 httpReq.URL.RawQuery = opQueryString 151 } 152 153 r := &Request{ 154 Config: cfg, 155 ClientInfo: clientInfo, 156 Handlers: handlers.Copy(), 157 158 Retryer: retryer, 159 Time: time.Now(), 160 ExpireTime: 0, 161 Operation: operation, 162 HTTPRequest: httpReq, 163 Body: nil, 164 Params: params, 165 Error: err, 166 Data: data, 167 } 168 r.SetBufferBody([]byte{}) 169 170 return r 171 } 172 173 // A Option is a functional option that can augment or modify a request when 174 // using a WithContext API operation method. 175 type Option func(*Request) 176 177 // WithGetResponseHeader builds a request Option which will retrieve a single 178 // header value from the HTTP Response. If there are multiple values for the 179 // header key use WithGetResponseHeaders instead to access the http.Header 180 // map directly. The passed in val pointer must be non-nil. 181 // 182 // This Option can be used multiple times with a single API operation. 183 // 184 // var id2, versionID string 185 // svc.PutObjectWithContext(ctx, params, 186 // request.WithGetResponseHeader("x-amz-id-2", &id2), 187 // request.WithGetResponseHeader("x-amz-version-id", &versionID), 188 // ) 189 func WithGetResponseHeader(key string, val *string) Option { 190 return func(r *Request) { 191 r.Handlers.Complete.PushBack(func(req *Request) { 192 *val = req.HTTPResponse.Header.Get(key) 193 }) 194 } 195 } 196 197 // WithGetResponseHeaders builds a request Option which will retrieve the 198 // headers from the HTTP response and assign them to the passed in headers 199 // variable. The passed in headers pointer must be non-nil. 200 // 201 // var headers http.Header 202 // svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers)) 203 func WithGetResponseHeaders(headers *http.Header) Option { 204 return func(r *Request) { 205 r.Handlers.Complete.PushBack(func(req *Request) { 206 *headers = req.HTTPResponse.Header 207 }) 208 } 209 } 210 211 // WithLogLevel is a request option that will set the request to use a specific 212 // log level when the request is made. 213 // 214 // svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody) 215 func WithLogLevel(l aws.LogLevelType) Option { 216 return func(r *Request) { 217 r.Config.LogLevel = aws.LogLevel(l) 218 } 219 } 220 221 // ApplyOptions will apply each option to the request calling them in the order 222 // the were provided. 223 func (r *Request) ApplyOptions(opts ...Option) { 224 for _, opt := range opts { 225 opt(r) 226 } 227 } 228 229 // Context will always returns a non-nil context. If Request does not have a 230 // context aws.BackgroundContext will be returned. 231 func (r *Request) Context() aws.Context { 232 if r.context != nil { 233 return r.context 234 } 235 return aws.BackgroundContext() 236 } 237 238 // SetContext adds a Context to the current request that can be used to cancel 239 // a in-flight request. The Context value must not be nil, or this method will 240 // panic. 241 // 242 // Unlike http.Request.WithContext, SetContext does not return a copy of the 243 // Request. It is not safe to use use a single Request value for multiple 244 // requests. A new Request should be created for each API operation request. 245 // 246 // Go 1.6 and below: 247 // The http.Request's Cancel field will be set to the Done() value of 248 // the context. This will overwrite the Cancel field's value. 249 // 250 // Go 1.7 and above: 251 // The http.Request.WithContext will be used to set the context on the underlying 252 // http.Request. This will create a shallow copy of the http.Request. The SDK 253 // may create sub contexts in the future for nested requests such as retries. 254 func (r *Request) SetContext(ctx aws.Context) { 255 if ctx == nil { 256 panic("context cannot be nil") 257 } 258 setRequestContext(r, ctx) 259 } 260 261 // WillRetry returns if the request's can be retried. 262 func (r *Request) WillRetry() bool { 263 if !aws.IsReaderSeekable(r.Body) && r.HTTPRequest.Body != NoBody { 264 return false 265 } 266 return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries() 267 } 268 269 func fmtAttemptCount(retryCount, maxRetries int) string { 270 return fmt.Sprintf("attempt %v/%v", retryCount, maxRetries) 271 } 272 273 // ParamsFilled returns if the request's parameters have been populated 274 // and the parameters are valid. False is returned if no parameters are 275 // provided or invalid. 276 func (r *Request) ParamsFilled() bool { 277 return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid() 278 } 279 280 // DataFilled returns true if the request's data for response deserialization 281 // target has been set and is a valid. False is returned if data is not 282 // set, or is invalid. 283 func (r *Request) DataFilled() bool { 284 return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid() 285 } 286 287 // SetBufferBody will set the request's body bytes that will be sent to 288 // the service API. 289 func (r *Request) SetBufferBody(buf []byte) { 290 r.SetReaderBody(bytes.NewReader(buf)) 291 } 292 293 // SetStringBody sets the body of the request to be backed by a string. 294 func (r *Request) SetStringBody(s string) { 295 r.SetReaderBody(strings.NewReader(s)) 296 } 297 298 // SetReaderBody will set the request's body reader. 299 func (r *Request) SetReaderBody(reader io.ReadSeeker) { 300 r.Body = reader 301 302 if aws.IsReaderSeekable(reader) { 303 var err error 304 // Get the Bodies current offset so retries will start from the same 305 // initial position. 306 r.BodyStart, err = reader.Seek(0, sdkio.SeekCurrent) 307 if err != nil { 308 r.Error = awserr.New(ErrCodeSerialization, 309 "failed to determine start of request body", err) 310 return 311 } 312 } 313 r.ResetBody() 314 } 315 316 // SetStreamingBody set the reader to be used for the request that will stream 317 // bytes to the server. Request's Body must not be set to any reader. 318 func (r *Request) SetStreamingBody(reader io.ReadCloser) { 319 r.streamingBody = reader 320 r.SetReaderBody(aws.ReadSeekCloser(reader)) 321 } 322 323 // Presign returns the request's signed URL. Error will be returned 324 // if the signing fails. The expire parameter is only used for presigned Amazon 325 // S3 API requests. All other AWS services will use a fixed expiration 326 // time of 15 minutes. 327 // 328 // It is invalid to create a presigned URL with a expire duration 0 or less. An 329 // error is returned if expire duration is 0 or less. 330 func (r *Request) Presign(expire time.Duration) (string, error) { 331 r = r.copy() 332 333 // Presign requires all headers be hoisted. There is no way to retrieve 334 // the signed headers not hoisted without this. Making the presigned URL 335 // useless. 336 r.NotHoist = false 337 338 u, _, err := getPresignedURL(r, expire) 339 return u, err 340 } 341 342 // PresignRequest behaves just like presign, with the addition of returning a 343 // set of headers that were signed. The expire parameter is only used for 344 // presigned Amazon S3 API requests. All other AWS services will use a fixed 345 // expiration time of 15 minutes. 346 // 347 // It is invalid to create a presigned URL with a expire duration 0 or less. An 348 // error is returned if expire duration is 0 or less. 349 // 350 // Returns the URL string for the API operation with signature in the query string, 351 // and the HTTP headers that were included in the signature. These headers must 352 // be included in any HTTP request made with the presigned URL. 353 // 354 // To prevent hoisting any headers to the query string set NotHoist to true on 355 // this Request value prior to calling PresignRequest. 356 func (r *Request) PresignRequest(expire time.Duration) (string, http.Header, error) { 357 r = r.copy() 358 return getPresignedURL(r, expire) 359 } 360 361 // IsPresigned returns true if the request represents a presigned API url. 362 func (r *Request) IsPresigned() bool { 363 return r.ExpireTime != 0 364 } 365 366 func getPresignedURL(r *Request, expire time.Duration) (string, http.Header, error) { 367 if expire <= 0 { 368 return "", nil, awserr.New( 369 ErrCodeInvalidPresignExpire, 370 "presigned URL requires an expire duration greater than 0", 371 nil, 372 ) 373 } 374 375 r.ExpireTime = expire 376 377 if r.Operation.BeforePresignFn != nil { 378 if err := r.Operation.BeforePresignFn(r); err != nil { 379 return "", nil, err 380 } 381 } 382 383 if err := r.Sign(); err != nil { 384 return "", nil, err 385 } 386 387 return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil 388 } 389 390 const ( 391 notRetrying = "not retrying" 392 ) 393 394 func debugLogReqError(r *Request, stage, retryStr string, err error) { 395 if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) { 396 return 397 } 398 399 r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v", 400 stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err)) 401 } 402 403 // Build will build the request's object so it can be signed and sent 404 // to the service. Build will also validate all the request's parameters. 405 // Any additional build Handlers set on this request will be run 406 // in the order they were set. 407 // 408 // The request will only be built once. Multiple calls to build will have 409 // no effect. 410 // 411 // If any Validate or Build errors occur the build will stop and the error 412 // which occurred will be returned. 413 func (r *Request) Build() error { 414 if !r.built { 415 r.Handlers.Validate.Run(r) 416 if r.Error != nil { 417 debugLogReqError(r, "Validate Request", notRetrying, r.Error) 418 return r.Error 419 } 420 r.Handlers.Build.Run(r) 421 if r.Error != nil { 422 debugLogReqError(r, "Build Request", notRetrying, r.Error) 423 return r.Error 424 } 425 r.built = true 426 } 427 428 return r.Error 429 } 430 431 // Sign will sign the request, returning error if errors are encountered. 432 // 433 // Sign will build the request prior to signing. All Sign Handlers will 434 // be executed in the order they were set. 435 func (r *Request) Sign() error { 436 r.Build() 437 if r.Error != nil { 438 debugLogReqError(r, "Build Request", notRetrying, r.Error) 439 return r.Error 440 } 441 442 SanitizeHostForHeader(r.HTTPRequest) 443 444 r.Handlers.Sign.Run(r) 445 return r.Error 446 } 447 448 func (r *Request) getNextRequestBody() (body io.ReadCloser, err error) { 449 if r.streamingBody != nil { 450 return r.streamingBody, nil 451 } 452 453 if r.safeBody != nil { 454 r.safeBody.Close() 455 } 456 457 r.safeBody, err = newOffsetReader(r.Body, r.BodyStart) 458 if err != nil { 459 return nil, awserr.New(ErrCodeSerialization, 460 "failed to get next request body reader", err) 461 } 462 463 // Go 1.8 tightened and clarified the rules code needs to use when building 464 // requests with the http package. Go 1.8 removed the automatic detection 465 // of if the Request.Body was empty, or actually had bytes in it. The SDK 466 // always sets the Request.Body even if it is empty and should not actually 467 // be sent. This is incorrect. 468 // 469 // Go 1.8 did add a http.NoBody value that the SDK can use to tell the http 470 // client that the request really should be sent without a body. The 471 // Request.Body cannot be set to nil, which is preferable, because the 472 // field is exported and could introduce nil pointer dereferences for users 473 // of the SDK if they used that field. 474 // 475 // Related golang/go#18257 476 l, err := aws.SeekerLen(r.Body) 477 if err != nil { 478 return nil, awserr.New(ErrCodeSerialization, 479 "failed to compute request body size", err) 480 } 481 482 if l == 0 { 483 body = NoBody 484 } else if l > 0 { 485 body = r.safeBody 486 } else { 487 // Hack to prevent sending bodies for methods where the body 488 // should be ignored by the server. Sending bodies on these 489 // methods without an associated ContentLength will cause the 490 // request to socket timeout because the server does not handle 491 // Transfer-Encoding: chunked bodies for these methods. 492 // 493 // This would only happen if a aws.ReaderSeekerCloser was used with 494 // a io.Reader that was not also an io.Seeker, or did not implement 495 // Len() method. 496 switch r.Operation.HTTPMethod { 497 case "GET", "HEAD", "DELETE": 498 body = NoBody 499 default: 500 body = r.safeBody 501 } 502 } 503 504 return body, nil 505 } 506 507 // GetBody will return an io.ReadSeeker of the Request's underlying 508 // input body with a concurrency safe wrapper. 509 func (r *Request) GetBody() io.ReadSeeker { 510 return r.safeBody 511 } 512 513 // Send will send the request, returning error if errors are encountered. 514 // 515 // Send will sign the request prior to sending. All Send Handlers will 516 // be executed in the order they were set. 517 // 518 // Canceling a request is non-deterministic. If a request has been canceled, 519 // then the transport will choose, randomly, one of the state channels during 520 // reads or getting the connection. 521 // 522 // readLoop() and getConn(req *Request, cm connectMethod) 523 // https://github.com/golang/go/blob/master/src/net/http/transport.go 524 // 525 // Send will not close the request.Request's body. 526 func (r *Request) Send() error { 527 defer func() { 528 // Regardless of success or failure of the request trigger the Complete 529 // request handlers. 530 r.Handlers.Complete.Run(r) 531 }() 532 533 if err := r.Error; err != nil { 534 return err 535 } 536 537 for { 538 r.Error = nil 539 r.AttemptTime = time.Now() 540 541 if err := r.Sign(); err != nil { 542 debugLogReqError(r, "Sign Request", notRetrying, err) 543 return err 544 } 545 546 if err := r.sendRequest(); err == nil { 547 return nil 548 } 549 r.Handlers.Retry.Run(r) 550 r.Handlers.AfterRetry.Run(r) 551 552 if r.Error != nil || !aws.BoolValue(r.Retryable) { 553 return r.Error 554 } 555 556 if err := r.prepareRetry(); err != nil { 557 r.Error = err 558 return err 559 } 560 } 561 } 562 563 func (r *Request) prepareRetry() error { 564 if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) { 565 r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d", 566 r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount)) 567 } 568 569 // The previous http.Request will have a reference to the r.Body 570 // and the HTTP Client's Transport may still be reading from 571 // the request's body even though the Client's Do returned. 572 r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil) 573 r.ResetBody() 574 if err := r.Error; err != nil { 575 return awserr.New(ErrCodeSerialization, 576 "failed to prepare body for retry", err) 577 578 } 579 580 // Closing response body to ensure that no response body is leaked 581 // between retry attempts. 582 if r.HTTPResponse != nil && r.HTTPResponse.Body != nil { 583 r.HTTPResponse.Body.Close() 584 } 585 586 return nil 587 } 588 589 func (r *Request) sendRequest() (sendErr error) { 590 defer r.Handlers.CompleteAttempt.Run(r) 591 592 r.Retryable = nil 593 r.Handlers.Send.Run(r) 594 if r.Error != nil { 595 debugLogReqError(r, "Send Request", 596 fmtAttemptCount(r.RetryCount, r.MaxRetries()), 597 r.Error) 598 return r.Error 599 } 600 601 r.Handlers.UnmarshalMeta.Run(r) 602 r.Handlers.ValidateResponse.Run(r) 603 if r.Error != nil { 604 r.Handlers.UnmarshalError.Run(r) 605 debugLogReqError(r, "Validate Response", 606 fmtAttemptCount(r.RetryCount, r.MaxRetries()), 607 r.Error) 608 return r.Error 609 } 610 611 r.Handlers.Unmarshal.Run(r) 612 if r.Error != nil { 613 debugLogReqError(r, "Unmarshal Response", 614 fmtAttemptCount(r.RetryCount, r.MaxRetries()), 615 r.Error) 616 return r.Error 617 } 618 619 return nil 620 } 621 622 // copy will copy a request which will allow for local manipulation of the 623 // request. 624 func (r *Request) copy() *Request { 625 req := &Request{} 626 *req = *r 627 req.Handlers = r.Handlers.Copy() 628 op := *r.Operation 629 req.Operation = &op 630 return req 631 } 632 633 // AddToUserAgent adds the string to the end of the request's current user agent. 634 func AddToUserAgent(r *Request, s string) { 635 curUA := r.HTTPRequest.Header.Get("User-Agent") 636 if len(curUA) > 0 { 637 s = curUA + " " + s 638 } 639 r.HTTPRequest.Header.Set("User-Agent", s) 640 } 641 642 // SanitizeHostForHeader removes default port from host and updates request.Host 643 func SanitizeHostForHeader(r *http.Request) { 644 host := getHost(r) 645 port := portOnly(host) 646 if port != "" && isDefaultPort(r.URL.Scheme, port) { 647 r.Host = stripPort(host) 648 } 649 } 650 651 // Returns host from request 652 func getHost(r *http.Request) string { 653 if r.Host != "" { 654 return r.Host 655 } 656 657 if r.URL == nil { 658 return "" 659 } 660 661 return r.URL.Host 662 } 663 664 // Hostname returns u.Host, without any port number. 665 // 666 // If Host is an IPv6 literal with a port number, Hostname returns the 667 // IPv6 literal without the square brackets. IPv6 literals may include 668 // a zone identifier. 669 // 670 // Copied from the Go 1.8 standard library (net/url) 671 func stripPort(hostport string) string { 672 colon := strings.IndexByte(hostport, ':') 673 if colon == -1 { 674 return hostport 675 } 676 if i := strings.IndexByte(hostport, ']'); i != -1 { 677 return strings.TrimPrefix(hostport[:i], "[") 678 } 679 return hostport[:colon] 680 } 681 682 // Port returns the port part of u.Host, without the leading colon. 683 // If u.Host doesn't contain a port, Port returns an empty string. 684 // 685 // Copied from the Go 1.8 standard library (net/url) 686 func portOnly(hostport string) string { 687 colon := strings.IndexByte(hostport, ':') 688 if colon == -1 { 689 return "" 690 } 691 if i := strings.Index(hostport, "]:"); i != -1 { 692 return hostport[i+len("]:"):] 693 } 694 if strings.Contains(hostport, "]") { 695 return "" 696 } 697 return hostport[colon+len(":"):] 698 } 699 700 // Returns true if the specified URI is using the standard port 701 // (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs) 702 func isDefaultPort(scheme, port string) bool { 703 if port == "" { 704 return true 705 } 706 707 lowerCaseScheme := strings.ToLower(scheme) 708 if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") { 709 return true 710 } 711 712 return false 713 }