]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package request |
2 | ||
3 | import ( | |
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 | ||
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 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. | |
38 | type 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. | |
75 | type 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. | |
90 | func 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. | |
129 | type 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 | // ) | |
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) | |
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)) | |
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 | |
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) | |
169 | func 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. | |
177 | func (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. | |
185 | func (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. | |
208 | func (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. | |
216 | func (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. | |
223 | func (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. | |
230 | func (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. | |
236 | func (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. | |
241 | func (r *Request) SetStringBody(s string) { | |
242 | r.SetReaderBody(strings.NewReader(s)) | |
243 | } | |
244 | ||
245 | // SetReaderBody will set the request's body reader. | |
246 | func (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. | |
253 | func (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 | |
274 | func (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 | ||
284 | func 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. | |
308 | func (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. | |
330 | func (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. | |
344 | func (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. | |
396 | func 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. | |
430 | func (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. | |
447 | func (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. | |
533 | func (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. | |
543 | func 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 | ||
551 | func 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 | } |