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