14 "github.com/aws/aws-sdk-go/aws"
15 "github.com/aws/aws-sdk-go/aws/awserr"
16 "github.com/aws/aws-sdk-go/aws/client/metadata"
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 recieved
29 ErrCodeResponseTimeout = "ResponseTimeout"
31 // CanceledErrorCode is the error code that will be returned by an
32 // API request that was canceled. Requests given a aws.Context may
33 // return this error when canceled.
34 CanceledErrorCode = "RequestCanceled"
37 // A Request is the service request to be made.
40 ClientInfo metadata.ClientInfo
45 ExpireTime time.Duration
47 HTTPRequest *http.Request
48 HTTPResponse *http.Response
50 BodyStart int64 // offset from beginning of Body that the request body starts
57 RetryDelay time.Duration
59 SignedHeaderVals http.Header
60 LastSignedAt time.Time
61 DisableFollowRedirects bool
67 // Need to persist an intermediate body between the input Body and HTTP
68 // request body because the HTTP Client's transport can maintain a reference
69 // to the HTTP request's body after the client has returned. This value is
70 // safe to use concurrently and wrap the input Body for each HTTP request.
71 safeBody *offsetReader
74 // An Operation is the service API operation to be made.
75 type Operation struct {
81 BeforePresignFn func(r *Request) error
84 // New returns a new Request pointer for the service API
85 // operation and parameters.
87 // Params is any value of input parameters to be the request payload.
88 // Data is pointer value to an object which the request's response
89 // payload will be deserialized to.
90 func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
91 retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
93 method := operation.HTTPMethod
98 httpReq, _ := http.NewRequest(method, "", nil)
101 httpReq.URL, err = url.Parse(clientInfo.Endpoint + operation.HTTPPath)
103 httpReq.URL = &url.URL{}
104 err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
109 ClientInfo: clientInfo,
110 Handlers: handlers.Copy(),
115 Operation: operation,
116 HTTPRequest: httpReq,
122 r.SetBufferBody([]byte{})
127 // A Option is a functional option that can augment or modify a request when
128 // using a WithContext API operation method.
129 type Option func(*Request)
131 // WithGetResponseHeader builds a request Option which will retrieve a single
132 // header value from the HTTP Response. If there are multiple values for the
133 // header key use WithGetResponseHeaders instead to access the http.Header
134 // map directly. The passed in val pointer must be non-nil.
136 // This Option can be used multiple times with a single API operation.
138 // var id2, versionID string
139 // svc.PutObjectWithContext(ctx, params,
140 // request.WithGetResponseHeader("x-amz-id-2", &id2),
141 // request.WithGetResponseHeader("x-amz-version-id", &versionID),
143 func WithGetResponseHeader(key string, val *string) Option {
144 return func(r *Request) {
145 r.Handlers.Complete.PushBack(func(req *Request) {
146 *val = req.HTTPResponse.Header.Get(key)
151 // WithGetResponseHeaders builds a request Option which will retrieve the
152 // headers from the HTTP response and assign them to the passed in headers
153 // variable. The passed in headers pointer must be non-nil.
155 // var headers http.Header
156 // svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers))
157 func WithGetResponseHeaders(headers *http.Header) Option {
158 return func(r *Request) {
159 r.Handlers.Complete.PushBack(func(req *Request) {
160 *headers = req.HTTPResponse.Header
165 // WithLogLevel is a request option that will set the request to use a specific
166 // log level when the request is made.
168 // svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody)
169 func WithLogLevel(l aws.LogLevelType) Option {
170 return func(r *Request) {
171 r.Config.LogLevel = aws.LogLevel(l)
175 // ApplyOptions will apply each option to the request calling them in the order
176 // the were provided.
177 func (r *Request) ApplyOptions(opts ...Option) {
178 for _, opt := range opts {
183 // Context will always returns a non-nil context. If Request does not have a
184 // context aws.BackgroundContext will be returned.
185 func (r *Request) Context() aws.Context {
186 if r.context != nil {
189 return aws.BackgroundContext()
192 // SetContext adds a Context to the current request that can be used to cancel
193 // a in-flight request. The Context value must not be nil, or this method will
196 // Unlike http.Request.WithContext, SetContext does not return a copy of the
197 // Request. It is not safe to use use a single Request value for multiple
198 // requests. A new Request should be created for each API operation request.
201 // The http.Request's Cancel field will be set to the Done() value of
202 // the context. This will overwrite the Cancel field's value.
205 // The http.Request.WithContext will be used to set the context on the underlying
206 // http.Request. This will create a shallow copy of the http.Request. The SDK
207 // may create sub contexts in the future for nested requests such as retries.
208 func (r *Request) SetContext(ctx aws.Context) {
210 panic("context cannot be nil")
212 setRequestContext(r, ctx)
215 // WillRetry returns if the request's can be retried.
216 func (r *Request) WillRetry() bool {
217 return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
220 // ParamsFilled returns if the request's parameters have been populated
221 // and the parameters are valid. False is returned if no parameters are
222 // provided or invalid.
223 func (r *Request) ParamsFilled() bool {
224 return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
227 // DataFilled returns true if the request's data for response deserialization
228 // target has been set and is a valid. False is returned if data is not
229 // set, or is invalid.
230 func (r *Request) DataFilled() bool {
231 return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
234 // SetBufferBody will set the request's body bytes that will be sent to
236 func (r *Request) SetBufferBody(buf []byte) {
237 r.SetReaderBody(bytes.NewReader(buf))
240 // SetStringBody sets the body of the request to be backed by a string.
241 func (r *Request) SetStringBody(s string) {
242 r.SetReaderBody(strings.NewReader(s))
245 // SetReaderBody will set the request's body reader.
246 func (r *Request) SetReaderBody(reader io.ReadSeeker) {
251 // Presign returns the request's signed URL. Error will be returned
252 // if the signing fails.
253 func (r *Request) Presign(expireTime time.Duration) (string, error) {
254 r.ExpireTime = expireTime
257 if r.Operation.BeforePresignFn != nil {
259 err := r.Operation.BeforePresignFn(r)
269 return r.HTTPRequest.URL.String(), nil
272 // PresignRequest behaves just like presign, but hoists all headers and signs them.
273 // Also returns the signed hash back to the user
274 func (r *Request) PresignRequest(expireTime time.Duration) (string, http.Header, error) {
275 r.ExpireTime = expireTime
279 return "", nil, r.Error
281 return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
284 func debugLogReqError(r *Request, stage string, retrying bool, err error) {
285 if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
289 retryStr := "not retrying"
291 retryStr = "will retry"
294 r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
295 stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
298 // Build will build the request's object so it can be signed and sent
299 // to the service. Build will also validate all the request's parameters.
300 // Anny additional build Handlers set on this request will be run
301 // in the order they were set.
303 // The request will only be built once. Multiple calls to build will have
306 // If any Validate or Build errors occur the build will stop and the error
307 // which occurred will be returned.
308 func (r *Request) Build() error {
310 r.Handlers.Validate.Run(r)
312 debugLogReqError(r, "Validate Request", false, r.Error)
315 r.Handlers.Build.Run(r)
317 debugLogReqError(r, "Build Request", false, r.Error)
326 // Sign will sign the request returning error if errors are encountered.
328 // Send will build the request prior to signing. All Sign Handlers will
329 // be executed in the order they were set.
330 func (r *Request) Sign() error {
333 debugLogReqError(r, "Build Request", false, r.Error)
337 r.Handlers.Sign.Run(r)
341 func (r *Request) getNextRequestBody() (io.ReadCloser, error) {
342 if r.safeBody != nil {
346 r.safeBody = newOffsetReader(r.Body, r.BodyStart)
348 // Go 1.8 tightened and clarified the rules code needs to use when building
349 // requests with the http package. Go 1.8 removed the automatic detection
350 // of if the Request.Body was empty, or actually had bytes in it. The SDK
351 // always sets the Request.Body even if it is empty and should not actually
352 // be sent. This is incorrect.
354 // Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
355 // client that the request really should be sent without a body. The
356 // Request.Body cannot be set to nil, which is preferable, because the
357 // field is exported and could introduce nil pointer dereferences for users
358 // of the SDK if they used that field.
360 // Related golang/go#18257
361 l, err := computeBodyLength(r.Body)
363 return nil, awserr.New(ErrCodeSerialization, "failed to compute request body size", err)
366 var body io.ReadCloser
372 // Hack to prevent sending bodies for methods where the body
373 // should be ignored by the server. Sending bodies on these
374 // methods without an associated ContentLength will cause the
375 // request to socket timeout because the server does not handle
376 // Transfer-Encoding: chunked bodies for these methods.
378 // This would only happen if a aws.ReaderSeekerCloser was used with
379 // a io.Reader that was not also an io.Seeker.
380 switch r.Operation.HTTPMethod {
381 case "GET", "HEAD", "DELETE":
391 // Attempts to compute the length of the body of the reader using the
392 // io.Seeker interface. If the value is not seekable because of being
393 // a ReaderSeekerCloser without an unerlying Seeker -1 will be returned.
394 // If no error occurs the length of the body will be returned.
395 func computeBodyLength(r io.ReadSeeker) (int64, error) {
397 // Determine if the seeker is actually seekable. ReaderSeekerCloser
398 // hides the fact that a io.Readers might not actually be seekable.
399 switch v := r.(type) {
400 case aws.ReaderSeekerCloser:
401 seekable = v.IsSeeker()
402 case *aws.ReaderSeekerCloser:
403 seekable = v.IsSeeker()
409 curOffset, err := r.Seek(0, 1)
414 endOffset, err := r.Seek(0, 2)
419 _, err = r.Seek(curOffset, 0)
424 return endOffset - curOffset, nil
427 // GetBody will return an io.ReadSeeker of the Request's underlying
428 // input body with a concurrency safe wrapper.
429 func (r *Request) GetBody() io.ReadSeeker {
433 // Send will send the request returning error if errors are encountered.
435 // Send will sign the request prior to sending. All Send Handlers will
436 // be executed in the order they were set.
438 // Canceling a request is non-deterministic. If a request has been canceled,
439 // then the transport will choose, randomly, one of the state channels during
440 // reads or getting the connection.
442 // readLoop() and getConn(req *Request, cm connectMethod)
443 // https://github.com/golang/go/blob/master/src/net/http/transport.go
445 // Send will not close the request.Request's body.
446 func (r *Request) Send() error {
448 // Regardless of success or failure of the request trigger the Complete
450 r.Handlers.Complete.Run(r)
454 if aws.BoolValue(r.Retryable) {
455 if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
456 r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
457 r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
460 // The previous http.Request will have a reference to the r.Body
461 // and the HTTP Client's Transport may still be reading from
462 // the request's body even though the Client's Do returned.
463 r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil)
466 // Closing response body to ensure that no response body is leaked
467 // between retry attempts.
468 if r.HTTPResponse != nil && r.HTTPResponse.Body != nil {
469 r.HTTPResponse.Body.Close()
480 r.Handlers.Send.Run(r)
482 if !shouldRetryCancel(r) {
487 r.Handlers.Retry.Run(r)
488 r.Handlers.AfterRetry.Run(r)
490 debugLogReqError(r, "Send Request", false, err)
493 debugLogReqError(r, "Send Request", true, err)
496 r.Handlers.UnmarshalMeta.Run(r)
497 r.Handlers.ValidateResponse.Run(r)
499 r.Handlers.UnmarshalError.Run(r)
502 r.Handlers.Retry.Run(r)
503 r.Handlers.AfterRetry.Run(r)
505 debugLogReqError(r, "Validate Response", false, err)
508 debugLogReqError(r, "Validate Response", true, err)
512 r.Handlers.Unmarshal.Run(r)
515 r.Handlers.Retry.Run(r)
516 r.Handlers.AfterRetry.Run(r)
518 debugLogReqError(r, "Unmarshal Response", false, err)
521 debugLogReqError(r, "Unmarshal Response", true, err)
531 // copy will copy a request which will allow for local manipulation of the
533 func (r *Request) copy() *Request {
536 req.Handlers = r.Handlers.Copy()
542 // AddToUserAgent adds the string to the end of the request's current user agent.
543 func AddToUserAgent(r *Request, s string) {
544 curUA := r.HTTPRequest.Header.Get("User-Agent")
548 r.HTTPRequest.Header.Set("User-Agent", s)
551 func shouldRetryCancel(r *Request) bool {
552 awsErr, ok := r.Error.(awserr.Error)
554 errStr := r.Error.Error()
556 if awsErr.Code() == CanceledErrorCode {
559 err := awsErr.OrigErr()
560 netErr, netOK := err.(net.Error)
561 timeoutErr = netOK && netErr.Temporary()
562 if urlErr, ok := err.(*url.Error); !timeoutErr && ok {
563 errStr = urlErr.Err.Error()
567 // There can be two types of canceled errors here.
568 // The first being a net.Error and the other being an error.
569 // If the request was timed out, we want to continue the retry
570 // process. Otherwise, return the canceled error.
572 (errStr != "net/http: request canceled" &&
573 errStr != "net/http: request canceled while waiting for connection")