1package request 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "reflect" 10 "strings" 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/aws/client/metadata" 16 "github.com/aws/aws-sdk-go/internal/sdkio" 17) 18 19const ( 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. 46type 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. 98type 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. 117func 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. 175type 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// ) 189func 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)) 203func 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) 215func 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. 223func (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. 231func (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. 254func (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. 262func (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 269func 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. 276func (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. 283func (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. 289func (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. 294func (r *Request) SetStringBody(s string) { 295 r.SetReaderBody(strings.NewReader(s)) 296} 297 298// SetReaderBody will set the request's body reader. 299func (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. 318func (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. 330func (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. 356func (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. 362func (r *Request) IsPresigned() bool { 363 return r.ExpireTime != 0 364} 365 366func 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 390const ( 391 notRetrying = "not retrying" 392) 393 394func 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. 413func (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. 435func (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 448func (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. 509func (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. 526func (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 563func (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 589func (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. 624func (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. 634func 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 643func 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 652func 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) 671func 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) 686func 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) 702func 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} 714