]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/aws/aws-sdk-go/aws/request/request.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / aws / aws-sdk-go / aws / request / request.go
CommitLineData
bae9f6d2
JC
1package request
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "net"
8 "net/http"
9 "net/url"
10 "reflect"
11 "strings"
12 "time"
13
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"
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 recieved
28 // during body reads.
29 ErrCodeResponseTimeout = "ResponseTimeout"
30
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"
35)
36
37// A Request is the service request to be made.
38type Request struct {
39 Config aws.Config
40 ClientInfo metadata.ClientInfo
41 Handlers Handlers
42
43 Retryer
44 Time time.Time
45 ExpireTime time.Duration
46 Operation *Operation
47 HTTPRequest *http.Request
48 HTTPResponse *http.Response
49 Body io.ReadSeeker
50 BodyStart int64 // offset from beginning of Body that the request body starts
51 Params interface{}
52 Error error
53 Data interface{}
54 RequestID string
55 RetryCount int
56 Retryable *bool
57 RetryDelay time.Duration
58 NotHoist bool
59 SignedHeaderVals http.Header
60 LastSignedAt time.Time
61 DisableFollowRedirects bool
62
63 context aws.Context
64
65 built bool
66
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
72}
73
74// An Operation is the service API operation to be made.
75type Operation struct {
76 Name string
77 HTTPMethod string
78 HTTPPath string
79 *Paginator
80
81 BeforePresignFn func(r *Request) error
82}
83
84// New returns a new Request pointer for the service API
85// operation and parameters.
86//
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.
90func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
91 retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
92
93 method := operation.HTTPMethod
94 if method == "" {
95 method = "POST"
96 }
97
98 httpReq, _ := http.NewRequest(method, "", nil)
99
100 var err error
101 httpReq.URL, err = url.Parse(clientInfo.Endpoint + operation.HTTPPath)
102 if err != nil {
103 httpReq.URL = &url.URL{}
104 err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
105 }
106
107 r := &Request{
108 Config: cfg,
109 ClientInfo: clientInfo,
110 Handlers: handlers.Copy(),
111
112 Retryer: retryer,
113 Time: time.Now(),
114 ExpireTime: 0,
115 Operation: operation,
116 HTTPRequest: httpReq,
117 Body: nil,
118 Params: params,
119 Error: err,
120 Data: data,
121 }
122 r.SetBufferBody([]byte{})
123
124 return r
125}
126
127// A Option is a functional option that can augment or modify a request when
128// using a WithContext API operation method.
129type Option func(*Request)
130
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.
135//
136// This Option can be used multiple times with a single API operation.
137//
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),
142// )
143func 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)
147 })
148 }
149}
150
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.
154//
155// var headers http.Header
156// svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers))
157func WithGetResponseHeaders(headers *http.Header) Option {
158 return func(r *Request) {
159 r.Handlers.Complete.PushBack(func(req *Request) {
160 *headers = req.HTTPResponse.Header
161 })
162 }
163}
164
165// WithLogLevel is a request option that will set the request to use a specific
166// log level when the request is made.
167//
168// svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody)
169func WithLogLevel(l aws.LogLevelType) Option {
170 return func(r *Request) {
171 r.Config.LogLevel = aws.LogLevel(l)
172 }
173}
174
175// ApplyOptions will apply each option to the request calling them in the order
176// the were provided.
177func (r *Request) ApplyOptions(opts ...Option) {
178 for _, opt := range opts {
179 opt(r)
180 }
181}
182
183// Context will always returns a non-nil context. If Request does not have a
184// context aws.BackgroundContext will be returned.
185func (r *Request) Context() aws.Context {
186 if r.context != nil {
187 return r.context
188 }
189 return aws.BackgroundContext()
190}
191
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
194// panic.
195//
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.
199//
200// Go 1.6 and below:
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.
203//
204// Go 1.7 and above:
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.
208func (r *Request) SetContext(ctx aws.Context) {
209 if ctx == nil {
210 panic("context cannot be nil")
211 }
212 setRequestContext(r, ctx)
213}
214
215// WillRetry returns if the request's can be retried.
216func (r *Request) WillRetry() bool {
217 return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
218}
219
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.
223func (r *Request) ParamsFilled() bool {
224 return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
225}
226
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.
230func (r *Request) DataFilled() bool {
231 return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
232}
233
234// SetBufferBody will set the request's body bytes that will be sent to
235// the service API.
236func (r *Request) SetBufferBody(buf []byte) {
237 r.SetReaderBody(bytes.NewReader(buf))
238}
239
240// SetStringBody sets the body of the request to be backed by a string.
241func (r *Request) SetStringBody(s string) {
242 r.SetReaderBody(strings.NewReader(s))
243}
244
245// SetReaderBody will set the request's body reader.
246func (r *Request) SetReaderBody(reader io.ReadSeeker) {
247 r.Body = reader
248 r.ResetBody()
249}
250
251// Presign returns the request's signed URL. Error will be returned
252// if the signing fails.
253func (r *Request) Presign(expireTime time.Duration) (string, error) {
254 r.ExpireTime = expireTime
255 r.NotHoist = false
256
257 if r.Operation.BeforePresignFn != nil {
258 r = r.copy()
259 err := r.Operation.BeforePresignFn(r)
260 if err != nil {
261 return "", err
262 }
263 }
264
265 r.Sign()
266 if r.Error != nil {
267 return "", r.Error
268 }
269 return r.HTTPRequest.URL.String(), nil
270}
271
272// PresignRequest behaves just like presign, but hoists all headers and signs them.
273// Also returns the signed hash back to the user
274func (r *Request) PresignRequest(expireTime time.Duration) (string, http.Header, error) {
275 r.ExpireTime = expireTime
276 r.NotHoist = true
277 r.Sign()
278 if r.Error != nil {
279 return "", nil, r.Error
280 }
281 return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
282}
283
284func debugLogReqError(r *Request, stage string, retrying bool, err error) {
285 if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
286 return
287 }
288
289 retryStr := "not retrying"
290 if retrying {
291 retryStr = "will retry"
292 }
293
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))
296}
297
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.
302//
303// The request will only be built once. Multiple calls to build will have
304// no effect.
305//
306// If any Validate or Build errors occur the build will stop and the error
307// which occurred will be returned.
308func (r *Request) Build() error {
309 if !r.built {
310 r.Handlers.Validate.Run(r)
311 if r.Error != nil {
312 debugLogReqError(r, "Validate Request", false, r.Error)
313 return r.Error
314 }
315 r.Handlers.Build.Run(r)
316 if r.Error != nil {
317 debugLogReqError(r, "Build Request", false, r.Error)
318 return r.Error
319 }
320 r.built = true
321 }
322
323 return r.Error
324}
325
326// Sign will sign the request returning error if errors are encountered.
327//
328// Send will build the request prior to signing. All Sign Handlers will
329// be executed in the order they were set.
330func (r *Request) Sign() error {
331 r.Build()
332 if r.Error != nil {
333 debugLogReqError(r, "Build Request", false, r.Error)
334 return r.Error
335 }
336
337 r.Handlers.Sign.Run(r)
338 return r.Error
339}
340
341// ResetBody rewinds the request body backto its starting position, and
342// set's the HTTP Request body reference. When the body is read prior
343// to being sent in the HTTP request it will need to be rewound.
344func (r *Request) ResetBody() {
345 if r.safeBody != nil {
346 r.safeBody.Close()
347 }
348
349 r.safeBody = newOffsetReader(r.Body, r.BodyStart)
350
351 // Go 1.8 tightened and clarified the rules code needs to use when building
352 // requests with the http package. Go 1.8 removed the automatic detection
353 // of if the Request.Body was empty, or actually had bytes in it. The SDK
354 // always sets the Request.Body even if it is empty and should not actually
355 // be sent. This is incorrect.
356 //
357 // Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
358 // client that the request really should be sent without a body. The
359 // Request.Body cannot be set to nil, which is preferable, because the
360 // field is exported and could introduce nil pointer dereferences for users
361 // of the SDK if they used that field.
362 //
363 // Related golang/go#18257
364 l, err := computeBodyLength(r.Body)
365 if err != nil {
366 r.Error = awserr.New(ErrCodeSerialization, "failed to compute request body size", err)
367 return
368 }
369
370 if l == 0 {
371 r.HTTPRequest.Body = noBodyReader
372 } else if l > 0 {
373 r.HTTPRequest.Body = r.safeBody
374 } else {
375 // Hack to prevent sending bodies for methods where the body
376 // should be ignored by the server. Sending bodies on these
377 // methods without an associated ContentLength will cause the
378 // request to socket timeout because the server does not handle
379 // Transfer-Encoding: chunked bodies for these methods.
380 //
381 // This would only happen if a aws.ReaderSeekerCloser was used with
382 // a io.Reader that was not also an io.Seeker.
383 switch r.Operation.HTTPMethod {
384 case "GET", "HEAD", "DELETE":
385 r.HTTPRequest.Body = noBodyReader
386 default:
387 r.HTTPRequest.Body = r.safeBody
388 }
389 }
390}
391
392// Attempts to compute the length of the body of the reader using the
393// io.Seeker interface. If the value is not seekable because of being
394// a ReaderSeekerCloser without an unerlying Seeker -1 will be returned.
395// If no error occurs the length of the body will be returned.
396func computeBodyLength(r io.ReadSeeker) (int64, error) {
397 seekable := true
398 // Determine if the seeker is actually seekable. ReaderSeekerCloser
399 // hides the fact that a io.Readers might not actually be seekable.
400 switch v := r.(type) {
401 case aws.ReaderSeekerCloser:
402 seekable = v.IsSeeker()
403 case *aws.ReaderSeekerCloser:
404 seekable = v.IsSeeker()
405 }
406 if !seekable {
407 return -1, nil
408 }
409
410 curOffset, err := r.Seek(0, 1)
411 if err != nil {
412 return 0, err
413 }
414
415 endOffset, err := r.Seek(0, 2)
416 if err != nil {
417 return 0, err
418 }
419
420 _, err = r.Seek(curOffset, 0)
421 if err != nil {
422 return 0, err
423 }
424
425 return endOffset - curOffset, nil
426}
427
428// GetBody will return an io.ReadSeeker of the Request's underlying
429// input body with a concurrency safe wrapper.
430func (r *Request) GetBody() io.ReadSeeker {
431 return r.safeBody
432}
433
434// Send will send the request returning error if errors are encountered.
435//
436// Send will sign the request prior to sending. All Send Handlers will
437// be executed in the order they were set.
438//
439// Canceling a request is non-deterministic. If a request has been canceled,
440// then the transport will choose, randomly, one of the state channels during
441// reads or getting the connection.
442//
443// readLoop() and getConn(req *Request, cm connectMethod)
444// https://github.com/golang/go/blob/master/src/net/http/transport.go
445//
446// Send will not close the request.Request's body.
447func (r *Request) Send() error {
448 defer func() {
449 // Regardless of success or failure of the request trigger the Complete
450 // request handlers.
451 r.Handlers.Complete.Run(r)
452 }()
453
454 for {
455 if aws.BoolValue(r.Retryable) {
456 if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
457 r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
458 r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
459 }
460
461 // The previous http.Request will have a reference to the r.Body
462 // and the HTTP Client's Transport may still be reading from
463 // the request's body even though the Client's Do returned.
464 r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil)
465 r.ResetBody()
466
467 // Closing response body to ensure that no response body is leaked
468 // between retry attempts.
469 if r.HTTPResponse != nil && r.HTTPResponse.Body != nil {
470 r.HTTPResponse.Body.Close()
471 }
472 }
473
474 r.Sign()
475 if r.Error != nil {
476 return r.Error
477 }
478
479 r.Retryable = nil
480
481 r.Handlers.Send.Run(r)
482 if r.Error != nil {
483 if !shouldRetryCancel(r) {
484 return r.Error
485 }
486
487 err := r.Error
488 r.Handlers.Retry.Run(r)
489 r.Handlers.AfterRetry.Run(r)
490 if r.Error != nil {
491 debugLogReqError(r, "Send Request", false, r.Error)
492 return r.Error
493 }
494 debugLogReqError(r, "Send Request", true, err)
495 continue
496 }
497 r.Handlers.UnmarshalMeta.Run(r)
498 r.Handlers.ValidateResponse.Run(r)
499 if r.Error != nil {
500 err := r.Error
501 r.Handlers.UnmarshalError.Run(r)
502 r.Handlers.Retry.Run(r)
503 r.Handlers.AfterRetry.Run(r)
504 if r.Error != nil {
505 debugLogReqError(r, "Validate Response", false, r.Error)
506 return r.Error
507 }
508 debugLogReqError(r, "Validate Response", true, err)
509 continue
510 }
511
512 r.Handlers.Unmarshal.Run(r)
513 if r.Error != nil {
514 err := r.Error
515 r.Handlers.Retry.Run(r)
516 r.Handlers.AfterRetry.Run(r)
517 if r.Error != nil {
518 debugLogReqError(r, "Unmarshal Response", false, r.Error)
519 return r.Error
520 }
521 debugLogReqError(r, "Unmarshal Response", true, err)
522 continue
523 }
524
525 break
526 }
527
528 return nil
529}
530
531// copy will copy a request which will allow for local manipulation of the
532// request.
533func (r *Request) copy() *Request {
534 req := &Request{}
535 *req = *r
536 req.Handlers = r.Handlers.Copy()
537 op := *r.Operation
538 req.Operation = &op
539 return req
540}
541
542// AddToUserAgent adds the string to the end of the request's current user agent.
543func AddToUserAgent(r *Request, s string) {
544 curUA := r.HTTPRequest.Header.Get("User-Agent")
545 if len(curUA) > 0 {
546 s = curUA + " " + s
547 }
548 r.HTTPRequest.Header.Set("User-Agent", s)
549}
550
551func shouldRetryCancel(r *Request) bool {
552 awsErr, ok := r.Error.(awserr.Error)
553 timeoutErr := false
554 errStr := r.Error.Error()
555 if ok {
556 if awsErr.Code() == CanceledErrorCode {
557 return false
558 }
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()
564 }
565 }
566
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.
571 return timeoutErr ||
572 (errStr != "net/http: request canceled" &&
573 errStr != "net/http: request canceled while waiting for connection")
574
575}