]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package corehandlers |
2 | ||
3 | import ( | |
4 | "bytes" | |
5 | "fmt" | |
bae9f6d2 JC |
6 | "io/ioutil" |
7 | "net/http" | |
8 | "net/url" | |
9 | "regexp" | |
bae9f6d2 JC |
10 | "strconv" |
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/credentials" | |
16 | "github.com/aws/aws-sdk-go/aws/request" | |
17 | ) | |
18 | ||
19 | // Interface for matching types which also have a Len method. | |
20 | type lener interface { | |
21 | Len() int | |
22 | } | |
23 | ||
24 | // BuildContentLengthHandler builds the content length of a request based on the body, | |
25 | // or will use the HTTPRequest.Header's "Content-Length" if defined. If unable | |
26 | // to determine request body length and no "Content-Length" was specified it will panic. | |
27 | // | |
28 | // The Content-Length will only be added to the request if the length of the body | |
29 | // is greater than 0. If the body is empty or the current `Content-Length` | |
30 | // header is <= 0, the header will also be stripped. | |
31 | var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLengthHandler", Fn: func(r *request.Request) { | |
32 | var length int64 | |
33 | ||
34 | if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" { | |
35 | length, _ = strconv.ParseInt(slength, 10, 64) | |
36 | } else { | |
15c0b25d AP |
37 | if r.Body != nil { |
38 | var err error | |
39 | length, err = aws.SeekerLen(r.Body) | |
40 | if err != nil { | |
41 | r.Error = awserr.New(request.ErrCodeSerialization, "failed to get request body's length", err) | |
42 | return | |
43 | } | |
bae9f6d2 JC |
44 | } |
45 | } | |
46 | ||
47 | if length > 0 { | |
48 | r.HTTPRequest.ContentLength = length | |
49 | r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length)) | |
50 | } else { | |
51 | r.HTTPRequest.ContentLength = 0 | |
52 | r.HTTPRequest.Header.Del("Content-Length") | |
53 | } | |
54 | }} | |
55 | ||
bae9f6d2 JC |
56 | var reStatusCode = regexp.MustCompile(`^(\d{3})`) |
57 | ||
58 | // ValidateReqSigHandler is a request handler to ensure that the request's | |
59 | // signature doesn't expire before it is sent. This can happen when a request | |
60 | // is built and signed significantly before it is sent. Or significant delays | |
61 | // occur when retrying requests that would cause the signature to expire. | |
62 | var ValidateReqSigHandler = request.NamedHandler{ | |
63 | Name: "core.ValidateReqSigHandler", | |
64 | Fn: func(r *request.Request) { | |
65 | // Unsigned requests are not signed | |
66 | if r.Config.Credentials == credentials.AnonymousCredentials { | |
67 | return | |
68 | } | |
69 | ||
70 | signedTime := r.Time | |
71 | if !r.LastSignedAt.IsZero() { | |
72 | signedTime = r.LastSignedAt | |
73 | } | |
74 | ||
107c1cdb | 75 | // 5 minutes to allow for some clock skew/delays in transmission. |
bae9f6d2 | 76 | // Would be improved with aws/aws-sdk-go#423 |
107c1cdb | 77 | if signedTime.Add(5 * time.Minute).After(time.Now()) { |
bae9f6d2 JC |
78 | return |
79 | } | |
80 | ||
81 | fmt.Println("request expired, resigning") | |
82 | r.Sign() | |
83 | }, | |
84 | } | |
85 | ||
86 | // SendHandler is a request handler to send service request using HTTP client. | |
87 | var SendHandler = request.NamedHandler{ | |
88 | Name: "core.SendHandler", | |
89 | Fn: func(r *request.Request) { | |
90 | sender := sendFollowRedirects | |
91 | if r.DisableFollowRedirects { | |
92 | sender = sendWithoutFollowRedirects | |
93 | } | |
94 | ||
9b12e4fe JC |
95 | if request.NoBody == r.HTTPRequest.Body { |
96 | // Strip off the request body if the NoBody reader was used as a | |
97 | // place holder for a request body. This prevents the SDK from | |
98 | // making requests with a request body when it would be invalid | |
99 | // to do so. | |
100 | // | |
101 | // Use a shallow copy of the http.Request to ensure the race condition | |
102 | // of transport on Body will not trigger | |
103 | reqOrig, reqCopy := r.HTTPRequest, *r.HTTPRequest | |
104 | reqCopy.Body = nil | |
105 | r.HTTPRequest = &reqCopy | |
106 | defer func() { | |
107 | r.HTTPRequest = reqOrig | |
108 | }() | |
109 | } | |
110 | ||
bae9f6d2 JC |
111 | var err error |
112 | r.HTTPResponse, err = sender(r) | |
113 | if err != nil { | |
114 | handleSendError(r, err) | |
115 | } | |
116 | }, | |
117 | } | |
118 | ||
119 | func sendFollowRedirects(r *request.Request) (*http.Response, error) { | |
120 | return r.Config.HTTPClient.Do(r.HTTPRequest) | |
121 | } | |
122 | ||
123 | func sendWithoutFollowRedirects(r *request.Request) (*http.Response, error) { | |
124 | transport := r.Config.HTTPClient.Transport | |
125 | if transport == nil { | |
126 | transport = http.DefaultTransport | |
127 | } | |
128 | ||
129 | return transport.RoundTrip(r.HTTPRequest) | |
130 | } | |
131 | ||
132 | func handleSendError(r *request.Request, err error) { | |
133 | // Prevent leaking if an HTTPResponse was returned. Clean up | |
134 | // the body. | |
135 | if r.HTTPResponse != nil { | |
136 | r.HTTPResponse.Body.Close() | |
137 | } | |
138 | // Capture the case where url.Error is returned for error processing | |
139 | // response. e.g. 301 without location header comes back as string | |
140 | // error and r.HTTPResponse is nil. Other URL redirect errors will | |
141 | // comeback in a similar method. | |
142 | if e, ok := err.(*url.Error); ok && e.Err != nil { | |
143 | if s := reStatusCode.FindStringSubmatch(e.Err.Error()); s != nil { | |
144 | code, _ := strconv.ParseInt(s[1], 10, 64) | |
145 | r.HTTPResponse = &http.Response{ | |
146 | StatusCode: int(code), | |
147 | Status: http.StatusText(int(code)), | |
148 | Body: ioutil.NopCloser(bytes.NewReader([]byte{})), | |
149 | } | |
150 | return | |
151 | } | |
152 | } | |
153 | if r.HTTPResponse == nil { | |
154 | // Add a dummy request response object to ensure the HTTPResponse | |
155 | // value is consistent. | |
156 | r.HTTPResponse = &http.Response{ | |
157 | StatusCode: int(0), | |
158 | Status: http.StatusText(int(0)), | |
159 | Body: ioutil.NopCloser(bytes.NewReader([]byte{})), | |
160 | } | |
161 | } | |
162 | // Catch all other request errors. | |
163 | r.Error = awserr.New("RequestError", "send request failed", err) | |
164 | r.Retryable = aws.Bool(true) // network errors are retryable | |
165 | ||
166 | // Override the error with a context canceled error, if that was canceled. | |
167 | ctx := r.Context() | |
168 | select { | |
169 | case <-ctx.Done(): | |
170 | r.Error = awserr.New(request.CanceledErrorCode, | |
171 | "request context canceled", ctx.Err()) | |
172 | r.Retryable = aws.Bool(false) | |
173 | default: | |
174 | } | |
175 | } | |
176 | ||
177 | // ValidateResponseHandler is a request handler to validate service response. | |
178 | var ValidateResponseHandler = request.NamedHandler{Name: "core.ValidateResponseHandler", Fn: func(r *request.Request) { | |
179 | if r.HTTPResponse.StatusCode == 0 || r.HTTPResponse.StatusCode >= 300 { | |
180 | // this may be replaced by an UnmarshalError handler | |
181 | r.Error = awserr.New("UnknownError", "unknown error", nil) | |
182 | } | |
183 | }} | |
184 | ||
185 | // AfterRetryHandler performs final checks to determine if the request should | |
186 | // be retried and how long to delay. | |
187 | var AfterRetryHandler = request.NamedHandler{Name: "core.AfterRetryHandler", Fn: func(r *request.Request) { | |
188 | // If one of the other handlers already set the retry state | |
189 | // we don't want to override it based on the service's state | |
190 | if r.Retryable == nil || aws.BoolValue(r.Config.EnforceShouldRetryCheck) { | |
191 | r.Retryable = aws.Bool(r.ShouldRetry(r)) | |
192 | } | |
193 | ||
194 | if r.WillRetry() { | |
195 | r.RetryDelay = r.RetryRules(r) | |
196 | ||
197 | if sleepFn := r.Config.SleepDelay; sleepFn != nil { | |
198 | // Support SleepDelay for backwards compatibility and testing | |
199 | sleepFn(r.RetryDelay) | |
200 | } else if err := aws.SleepWithContext(r.Context(), r.RetryDelay); err != nil { | |
201 | r.Error = awserr.New(request.CanceledErrorCode, | |
202 | "request context canceled", err) | |
203 | r.Retryable = aws.Bool(false) | |
204 | return | |
205 | } | |
206 | ||
207 | // when the expired token exception occurs the credentials | |
208 | // need to be expired locally so that the next request to | |
209 | // get credentials will trigger a credentials refresh. | |
210 | if r.IsErrorExpired() { | |
211 | r.Config.Credentials.Expire() | |
212 | } | |
213 | ||
214 | r.RetryCount++ | |
215 | r.Error = nil | |
216 | } | |
217 | }} | |
218 | ||
219 | // ValidateEndpointHandler is a request handler to validate a request had the | |
220 | // appropriate Region and Endpoint set. Will set r.Error if the endpoint or | |
221 | // region is not valid. | |
222 | var ValidateEndpointHandler = request.NamedHandler{Name: "core.ValidateEndpointHandler", Fn: func(r *request.Request) { | |
223 | if r.ClientInfo.SigningRegion == "" && aws.StringValue(r.Config.Region) == "" { | |
224 | r.Error = aws.ErrMissingRegion | |
225 | } else if r.ClientInfo.Endpoint == "" { | |
226 | r.Error = aws.ErrMissingEndpoint | |
227 | } | |
228 | }} |