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"
20 // ErrCodeSerialization is the serialization error code that is received
21 // during protocol unmarshaling.
22 ErrCodeSerialization = "SerializationError"
24 // ErrCodeRead is an error that is returned during HTTP reads.
25 ErrCodeRead = "ReadError"
27 // ErrCodeResponseTimeout is the connection timeout error that is received
29 ErrCodeResponseTimeout = "ResponseTimeout"
31 // ErrCodeInvalidPresignExpire is returned when the expire time provided to
33 ErrCodeInvalidPresignExpire = "InvalidPresignExpireError"
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"
41 // A Request is the service request to be made.
44 ClientInfo metadata.ClientInfo
51 HTTPRequest *http.Request
52 HTTPResponse *http.Response
54 BodyStart int64 // offset from beginning of Body that the request body starts
61 RetryDelay time.Duration
63 SignedHeaderVals http.Header
64 LastSignedAt time.Time
65 DisableFollowRedirects bool
67 // A value greater than 0 instructs the request to be signed as Presigned URL
68 // You should not set this field directly. Instead use Request's
69 // Presign or PresignRequest methods.
70 ExpireTime time.Duration
76 // Need to persist an intermediate body between the input Body and HTTP
77 // request body because the HTTP Client's transport can maintain a reference
78 // to the HTTP request's body after the client has returned. This value is
79 // safe to use concurrently and wrap the input Body for each HTTP request.
80 safeBody *offsetReader
83 // An Operation is the service API operation to be made.
84 type Operation struct {
90 BeforePresignFn func(r *Request) error
93 // New returns a new Request pointer for the service API
94 // operation and parameters.
96 // Params is any value of input parameters to be the request payload.
97 // Data is pointer value to an object which the request's response
98 // payload will be deserialized to.
99 func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
100 retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
102 method := operation.HTTPMethod
107 httpReq, _ := http.NewRequest(method, "", nil)
110 httpReq.URL, err = url.Parse(clientInfo.Endpoint + operation.HTTPPath)
112 httpReq.URL = &url.URL{}
113 err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
116 SanitizeHostForHeader(httpReq)
120 ClientInfo: clientInfo,
121 Handlers: handlers.Copy(),
126 Operation: operation,
127 HTTPRequest: httpReq,
133 r.SetBufferBody([]byte{})
138 // A Option is a functional option that can augment or modify a request when
139 // using a WithContext API operation method.
140 type Option func(*Request)
142 // WithGetResponseHeader builds a request Option which will retrieve a single
143 // header value from the HTTP Response. If there are multiple values for the
144 // header key use WithGetResponseHeaders instead to access the http.Header
145 // map directly. The passed in val pointer must be non-nil.
147 // This Option can be used multiple times with a single API operation.
149 // var id2, versionID string
150 // svc.PutObjectWithContext(ctx, params,
151 // request.WithGetResponseHeader("x-amz-id-2", &id2),
152 // request.WithGetResponseHeader("x-amz-version-id", &versionID),
154 func WithGetResponseHeader(key string, val *string) Option {
155 return func(r *Request) {
156 r.Handlers.Complete.PushBack(func(req *Request) {
157 *val = req.HTTPResponse.Header.Get(key)
162 // WithGetResponseHeaders builds a request Option which will retrieve the
163 // headers from the HTTP response and assign them to the passed in headers
164 // variable. The passed in headers pointer must be non-nil.
166 // var headers http.Header
167 // svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers))
168 func WithGetResponseHeaders(headers *http.Header) Option {
169 return func(r *Request) {
170 r.Handlers.Complete.PushBack(func(req *Request) {
171 *headers = req.HTTPResponse.Header
176 // WithLogLevel is a request option that will set the request to use a specific
177 // log level when the request is made.
179 // svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody)
180 func WithLogLevel(l aws.LogLevelType) Option {
181 return func(r *Request) {
182 r.Config.LogLevel = aws.LogLevel(l)
186 // ApplyOptions will apply each option to the request calling them in the order
187 // the were provided.
188 func (r *Request) ApplyOptions(opts ...Option) {
189 for _, opt := range opts {
194 // Context will always returns a non-nil context. If Request does not have a
195 // context aws.BackgroundContext will be returned.
196 func (r *Request) Context() aws.Context {
197 if r.context != nil {
200 return aws.BackgroundContext()
203 // SetContext adds a Context to the current request that can be used to cancel
204 // a in-flight request. The Context value must not be nil, or this method will
207 // Unlike http.Request.WithContext, SetContext does not return a copy of the
208 // Request. It is not safe to use use a single Request value for multiple
209 // requests. A new Request should be created for each API operation request.
212 // The http.Request's Cancel field will be set to the Done() value of
213 // the context. This will overwrite the Cancel field's value.
216 // The http.Request.WithContext will be used to set the context on the underlying
217 // http.Request. This will create a shallow copy of the http.Request. The SDK
218 // may create sub contexts in the future for nested requests such as retries.
219 func (r *Request) SetContext(ctx aws.Context) {
221 panic("context cannot be nil")
223 setRequestContext(r, ctx)
226 // WillRetry returns if the request's can be retried.
227 func (r *Request) WillRetry() bool {
228 if !aws.IsReaderSeekable(r.Body) && r.HTTPRequest.Body != NoBody {
231 return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
234 // ParamsFilled returns if the request's parameters have been populated
235 // and the parameters are valid. False is returned if no parameters are
236 // provided or invalid.
237 func (r *Request) ParamsFilled() bool {
238 return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
241 // DataFilled returns true if the request's data for response deserialization
242 // target has been set and is a valid. False is returned if data is not
243 // set, or is invalid.
244 func (r *Request) DataFilled() bool {
245 return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
248 // SetBufferBody will set the request's body bytes that will be sent to
250 func (r *Request) SetBufferBody(buf []byte) {
251 r.SetReaderBody(bytes.NewReader(buf))
254 // SetStringBody sets the body of the request to be backed by a string.
255 func (r *Request) SetStringBody(s string) {
256 r.SetReaderBody(strings.NewReader(s))
259 // SetReaderBody will set the request's body reader.
260 func (r *Request) SetReaderBody(reader io.ReadSeeker) {
262 r.BodyStart, _ = reader.Seek(0, sdkio.SeekCurrent) // Get the Bodies current offset.
266 // Presign returns the request's signed URL. Error will be returned
267 // if the signing fails. The expire parameter is only used for presigned Amazon
268 // S3 API requests. All other AWS services will use a fixed expiration
269 // time of 15 minutes.
271 // It is invalid to create a presigned URL with a expire duration 0 or less. An
272 // error is returned if expire duration is 0 or less.
273 func (r *Request) Presign(expire time.Duration) (string, error) {
276 // Presign requires all headers be hoisted. There is no way to retrieve
277 // the signed headers not hoisted without this. Making the presigned URL
281 u, _, err := getPresignedURL(r, expire)
285 // PresignRequest behaves just like presign, with the addition of returning a
286 // set of headers that were signed. The expire parameter is only used for
287 // presigned Amazon S3 API requests. All other AWS services will use a fixed
288 // expiration time of 15 minutes.
290 // It is invalid to create a presigned URL with a expire duration 0 or less. An
291 // error is returned if expire duration is 0 or less.
293 // Returns the URL string for the API operation with signature in the query string,
294 // and the HTTP headers that were included in the signature. These headers must
295 // be included in any HTTP request made with the presigned URL.
297 // To prevent hoisting any headers to the query string set NotHoist to true on
298 // this Request value prior to calling PresignRequest.
299 func (r *Request) PresignRequest(expire time.Duration) (string, http.Header, error) {
301 return getPresignedURL(r, expire)
304 // IsPresigned returns true if the request represents a presigned API url.
305 func (r *Request) IsPresigned() bool {
306 return r.ExpireTime != 0
309 func getPresignedURL(r *Request, expire time.Duration) (string, http.Header, error) {
311 return "", nil, awserr.New(
312 ErrCodeInvalidPresignExpire,
313 "presigned URL requires an expire duration greater than 0",
318 r.ExpireTime = expire
320 if r.Operation.BeforePresignFn != nil {
321 if err := r.Operation.BeforePresignFn(r); err != nil {
326 if err := r.Sign(); err != nil {
330 return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
333 func debugLogReqError(r *Request, stage string, retrying bool, err error) {
334 if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
338 retryStr := "not retrying"
340 retryStr = "will retry"
343 r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
344 stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
347 // Build will build the request's object so it can be signed and sent
348 // to the service. Build will also validate all the request's parameters.
349 // Any additional build Handlers set on this request will be run
350 // in the order they were set.
352 // The request will only be built once. Multiple calls to build will have
355 // If any Validate or Build errors occur the build will stop and the error
356 // which occurred will be returned.
357 func (r *Request) Build() error {
359 r.Handlers.Validate.Run(r)
361 debugLogReqError(r, "Validate Request", false, r.Error)
364 r.Handlers.Build.Run(r)
366 debugLogReqError(r, "Build Request", false, r.Error)
375 // Sign will sign the request, returning error if errors are encountered.
377 // Sign will build the request prior to signing. All Sign Handlers will
378 // be executed in the order they were set.
379 func (r *Request) Sign() error {
382 debugLogReqError(r, "Build Request", false, r.Error)
386 r.Handlers.Sign.Run(r)
390 func (r *Request) getNextRequestBody() (io.ReadCloser, error) {
391 if r.safeBody != nil {
395 r.safeBody = newOffsetReader(r.Body, r.BodyStart)
397 // Go 1.8 tightened and clarified the rules code needs to use when building
398 // requests with the http package. Go 1.8 removed the automatic detection
399 // of if the Request.Body was empty, or actually had bytes in it. The SDK
400 // always sets the Request.Body even if it is empty and should not actually
401 // be sent. This is incorrect.
403 // Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
404 // client that the request really should be sent without a body. The
405 // Request.Body cannot be set to nil, which is preferable, because the
406 // field is exported and could introduce nil pointer dereferences for users
407 // of the SDK if they used that field.
409 // Related golang/go#18257
410 l, err := aws.SeekerLen(r.Body)
412 return nil, awserr.New(ErrCodeSerialization, "failed to compute request body size", err)
415 var body io.ReadCloser
421 // Hack to prevent sending bodies for methods where the body
422 // should be ignored by the server. Sending bodies on these
423 // methods without an associated ContentLength will cause the
424 // request to socket timeout because the server does not handle
425 // Transfer-Encoding: chunked bodies for these methods.
427 // This would only happen if a aws.ReaderSeekerCloser was used with
428 // a io.Reader that was not also an io.Seeker, or did not implement
430 switch r.Operation.HTTPMethod {
431 case "GET", "HEAD", "DELETE":
441 // GetBody will return an io.ReadSeeker of the Request's underlying
442 // input body with a concurrency safe wrapper.
443 func (r *Request) GetBody() io.ReadSeeker {
447 // Send will send the request, returning error if errors are encountered.
449 // Send will sign the request prior to sending. All Send Handlers will
450 // be executed in the order they were set.
452 // Canceling a request is non-deterministic. If a request has been canceled,
453 // then the transport will choose, randomly, one of the state channels during
454 // reads or getting the connection.
456 // readLoop() and getConn(req *Request, cm connectMethod)
457 // https://github.com/golang/go/blob/master/src/net/http/transport.go
459 // Send will not close the request.Request's body.
460 func (r *Request) Send() error {
462 // Regardless of success or failure of the request trigger the Complete
464 r.Handlers.Complete.Run(r)
467 if err := r.Error; err != nil {
473 r.AttemptTime = time.Now()
475 if err := r.Sign(); err != nil {
476 debugLogReqError(r, "Sign Request", false, err)
480 if err := r.sendRequest(); err == nil {
482 } else if !shouldRetryCancel(r.Error) {
485 r.Handlers.Retry.Run(r)
486 r.Handlers.AfterRetry.Run(r)
488 if r.Error != nil || !aws.BoolValue(r.Retryable) {
498 func (r *Request) prepareRetry() {
499 if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
500 r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
501 r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
504 // The previous http.Request will have a reference to the r.Body
505 // and the HTTP Client's Transport may still be reading from
506 // the request's body even though the Client's Do returned.
507 r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil)
510 // Closing response body to ensure that no response body is leaked
511 // between retry attempts.
512 if r.HTTPResponse != nil && r.HTTPResponse.Body != nil {
513 r.HTTPResponse.Body.Close()
517 func (r *Request) sendRequest() (sendErr error) {
518 defer r.Handlers.CompleteAttempt.Run(r)
521 r.Handlers.Send.Run(r)
523 debugLogReqError(r, "Send Request", r.WillRetry(), r.Error)
527 r.Handlers.UnmarshalMeta.Run(r)
528 r.Handlers.ValidateResponse.Run(r)
530 r.Handlers.UnmarshalError.Run(r)
531 debugLogReqError(r, "Validate Response", r.WillRetry(), r.Error)
535 r.Handlers.Unmarshal.Run(r)
537 debugLogReqError(r, "Unmarshal Response", r.WillRetry(), r.Error)
544 // copy will copy a request which will allow for local manipulation of the
546 func (r *Request) copy() *Request {
549 req.Handlers = r.Handlers.Copy()
555 // AddToUserAgent adds the string to the end of the request's current user agent.
556 func AddToUserAgent(r *Request, s string) {
557 curUA := r.HTTPRequest.Header.Get("User-Agent")
561 r.HTTPRequest.Header.Set("User-Agent", s)
564 type temporary interface {
568 func shouldRetryCancel(err error) bool {
569 switch err := err.(type) {
571 if err.Code() == CanceledErrorCode {
574 return shouldRetryCancel(err.OrigErr())
576 if strings.Contains(err.Error(), "connection refused") {
577 // Refused connections should be retried as the service may not yet
578 // be running on the port. Go TCP dial considers refused
579 // connections as not temporary.
582 // *url.Error only implements Temporary after golang 1.6 but since
583 // url.Error only wraps the error:
584 return shouldRetryCancel(err.Err)
586 // If the error is temporary, we want to allow continuation of the
588 return err.Temporary()
590 // `awserr.Error.OrigErr()` can be nil, meaning there was an error but
591 // because we don't know the cause, it is marked as retriable. See
592 // TestRequest4xxUnretryable for an example.
596 case "net/http: request canceled",
597 "net/http: request canceled while waiting for connection":
598 // known 1.5 error case when an http request is cancelled
601 // here we don't know the error; so we allow a retry.
606 // SanitizeHostForHeader removes default port from host and updates request.Host
607 func SanitizeHostForHeader(r *http.Request) {
609 port := portOnly(host)
610 if port != "" && isDefaultPort(r.URL.Scheme, port) {
611 r.Host = stripPort(host)
615 // Returns host from request
616 func getHost(r *http.Request) string {
624 // Hostname returns u.Host, without any port number.
626 // If Host is an IPv6 literal with a port number, Hostname returns the
627 // IPv6 literal without the square brackets. IPv6 literals may include
628 // a zone identifier.
630 // Copied from the Go 1.8 standard library (net/url)
631 func stripPort(hostport string) string {
632 colon := strings.IndexByte(hostport, ':')
636 if i := strings.IndexByte(hostport, ']'); i != -1 {
637 return strings.TrimPrefix(hostport[:i], "[")
639 return hostport[:colon]
642 // Port returns the port part of u.Host, without the leading colon.
643 // If u.Host doesn't contain a port, Port returns an empty string.
645 // Copied from the Go 1.8 standard library (net/url)
646 func portOnly(hostport string) string {
647 colon := strings.IndexByte(hostport, ':')
651 if i := strings.Index(hostport, "]:"); i != -1 {
652 return hostport[i+len("]:"):]
654 if strings.Contains(hostport, "]") {
657 return hostport[colon+len(":"):]
660 // Returns true if the specified URI is using the standard port
661 // (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs)
662 func isDefaultPort(scheme, port string) bool {
667 lowerCaseScheme := strings.ToLower(scheme)
668 if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") {