]>
Commit | Line | Data |
---|---|---|
1 | package request | |
2 | ||
3 | import ( | |
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 | ||
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 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 | ||
41 | // A Request is the service request to be made. | |
42 | type Request struct { | |
43 | Config aws.Config | |
44 | ClientInfo metadata.ClientInfo | |
45 | Handlers Handlers | |
46 | ||
47 | Retryer | |
48 | AttemptTime time.Time | |
49 | Time time.Time | |
50 | Operation *Operation | |
51 | HTTPRequest *http.Request | |
52 | HTTPResponse *http.Response | |
53 | Body io.ReadSeeker | |
54 | BodyStart int64 // offset from beginning of Body that the request body starts | |
55 | Params interface{} | |
56 | Error error | |
57 | Data interface{} | |
58 | RequestID string | |
59 | RetryCount int | |
60 | Retryable *bool | |
61 | RetryDelay time.Duration | |
62 | NotHoist bool | |
63 | SignedHeaderVals http.Header | |
64 | LastSignedAt time.Time | |
65 | DisableFollowRedirects bool | |
66 | ||
67 | // A value greater than 0 instructs the request to be signed as Presigned URL | |
68 | // You should not set this field directly. Instead use Request's | |
69 | // Presign or PresignRequest methods. | |
70 | ExpireTime time.Duration | |
71 | ||
72 | context aws.Context | |
73 | ||
74 | built bool | |
75 | ||
76 | // Need to persist an intermediate body between the input Body and HTTP | |
77 | // request body because the HTTP Client's transport can maintain a reference | |
78 | // to the HTTP request's body after the client has returned. This value is | |
79 | // safe to use concurrently and wrap the input Body for each HTTP request. | |
80 | safeBody *offsetReader | |
81 | } | |
82 | ||
83 | // An Operation is the service API operation to be made. | |
84 | type Operation struct { | |
85 | Name string | |
86 | HTTPMethod string | |
87 | HTTPPath string | |
88 | *Paginator | |
89 | ||
90 | BeforePresignFn func(r *Request) error | |
91 | } | |
92 | ||
93 | // New returns a new Request pointer for the service API | |
94 | // operation and parameters. | |
95 | // | |
96 | // Params is any value of input parameters to be the request payload. | |
97 | // Data is pointer value to an object which the request's response | |
98 | // payload will be deserialized to. | |
99 | func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers, | |
100 | retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request { | |
101 | ||
102 | method := operation.HTTPMethod | |
103 | if method == "" { | |
104 | method = "POST" | |
105 | } | |
106 | ||
107 | httpReq, _ := http.NewRequest(method, "", nil) | |
108 | ||
109 | var err error | |
110 | httpReq.URL, err = url.Parse(clientInfo.Endpoint + operation.HTTPPath) | |
111 | if err != nil { | |
112 | httpReq.URL = &url.URL{} | |
113 | err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err) | |
114 | } | |
115 | ||
116 | SanitizeHostForHeader(httpReq) | |
117 | ||
118 | r := &Request{ | |
119 | Config: cfg, | |
120 | ClientInfo: clientInfo, | |
121 | Handlers: handlers.Copy(), | |
122 | ||
123 | Retryer: retryer, | |
124 | Time: time.Now(), | |
125 | ExpireTime: 0, | |
126 | Operation: operation, | |
127 | HTTPRequest: httpReq, | |
128 | Body: nil, | |
129 | Params: params, | |
130 | Error: err, | |
131 | Data: data, | |
132 | } | |
133 | r.SetBufferBody([]byte{}) | |
134 | ||
135 | return r | |
136 | } | |
137 | ||
138 | // A Option is a functional option that can augment or modify a request when | |
139 | // using a WithContext API operation method. | |
140 | type Option func(*Request) | |
141 | ||
142 | // WithGetResponseHeader builds a request Option which will retrieve a single | |
143 | // header value from the HTTP Response. If there are multiple values for the | |
144 | // header key use WithGetResponseHeaders instead to access the http.Header | |
145 | // map directly. The passed in val pointer must be non-nil. | |
146 | // | |
147 | // This Option can be used multiple times with a single API operation. | |
148 | // | |
149 | // var id2, versionID string | |
150 | // svc.PutObjectWithContext(ctx, params, | |
151 | // request.WithGetResponseHeader("x-amz-id-2", &id2), | |
152 | // request.WithGetResponseHeader("x-amz-version-id", &versionID), | |
153 | // ) | |
154 | func WithGetResponseHeader(key string, val *string) Option { | |
155 | return func(r *Request) { | |
156 | r.Handlers.Complete.PushBack(func(req *Request) { | |
157 | *val = req.HTTPResponse.Header.Get(key) | |
158 | }) | |
159 | } | |
160 | } | |
161 | ||
162 | // WithGetResponseHeaders builds a request Option which will retrieve the | |
163 | // headers from the HTTP response and assign them to the passed in headers | |
164 | // variable. The passed in headers pointer must be non-nil. | |
165 | // | |
166 | // var headers http.Header | |
167 | // svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers)) | |
168 | func WithGetResponseHeaders(headers *http.Header) Option { | |
169 | return func(r *Request) { | |
170 | r.Handlers.Complete.PushBack(func(req *Request) { | |
171 | *headers = req.HTTPResponse.Header | |
172 | }) | |
173 | } | |
174 | } | |
175 | ||
176 | // WithLogLevel is a request option that will set the request to use a specific | |
177 | // log level when the request is made. | |
178 | // | |
179 | // svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody) | |
180 | func WithLogLevel(l aws.LogLevelType) Option { | |
181 | return func(r *Request) { | |
182 | r.Config.LogLevel = aws.LogLevel(l) | |
183 | } | |
184 | } | |
185 | ||
186 | // ApplyOptions will apply each option to the request calling them in the order | |
187 | // the were provided. | |
188 | func (r *Request) ApplyOptions(opts ...Option) { | |
189 | for _, opt := range opts { | |
190 | opt(r) | |
191 | } | |
192 | } | |
193 | ||
194 | // Context will always returns a non-nil context. If Request does not have a | |
195 | // context aws.BackgroundContext will be returned. | |
196 | func (r *Request) Context() aws.Context { | |
197 | if r.context != nil { | |
198 | return r.context | |
199 | } | |
200 | return aws.BackgroundContext() | |
201 | } | |
202 | ||
203 | // SetContext adds a Context to the current request that can be used to cancel | |
204 | // a in-flight request. The Context value must not be nil, or this method will | |
205 | // panic. | |
206 | // | |
207 | // Unlike http.Request.WithContext, SetContext does not return a copy of the | |
208 | // Request. It is not safe to use use a single Request value for multiple | |
209 | // requests. A new Request should be created for each API operation request. | |
210 | // | |
211 | // Go 1.6 and below: | |
212 | // The http.Request's Cancel field will be set to the Done() value of | |
213 | // the context. This will overwrite the Cancel field's value. | |
214 | // | |
215 | // Go 1.7 and above: | |
216 | // The http.Request.WithContext will be used to set the context on the underlying | |
217 | // http.Request. This will create a shallow copy of the http.Request. The SDK | |
218 | // may create sub contexts in the future for nested requests such as retries. | |
219 | func (r *Request) SetContext(ctx aws.Context) { | |
220 | if ctx == nil { | |
221 | panic("context cannot be nil") | |
222 | } | |
223 | setRequestContext(r, ctx) | |
224 | } | |
225 | ||
226 | // WillRetry returns if the request's can be retried. | |
227 | func (r *Request) WillRetry() bool { | |
228 | if !aws.IsReaderSeekable(r.Body) && r.HTTPRequest.Body != NoBody { | |
229 | return false | |
230 | } | |
231 | return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries() | |
232 | } | |
233 | ||
234 | // ParamsFilled returns if the request's parameters have been populated | |
235 | // and the parameters are valid. False is returned if no parameters are | |
236 | // provided or invalid. | |
237 | func (r *Request) ParamsFilled() bool { | |
238 | return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid() | |
239 | } | |
240 | ||
241 | // DataFilled returns true if the request's data for response deserialization | |
242 | // target has been set and is a valid. False is returned if data is not | |
243 | // set, or is invalid. | |
244 | func (r *Request) DataFilled() bool { | |
245 | return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid() | |
246 | } | |
247 | ||
248 | // SetBufferBody will set the request's body bytes that will be sent to | |
249 | // the service API. | |
250 | func (r *Request) SetBufferBody(buf []byte) { | |
251 | r.SetReaderBody(bytes.NewReader(buf)) | |
252 | } | |
253 | ||
254 | // SetStringBody sets the body of the request to be backed by a string. | |
255 | func (r *Request) SetStringBody(s string) { | |
256 | r.SetReaderBody(strings.NewReader(s)) | |
257 | } | |
258 | ||
259 | // SetReaderBody will set the request's body reader. | |
260 | func (r *Request) SetReaderBody(reader io.ReadSeeker) { | |
261 | r.Body = reader | |
262 | r.BodyStart, _ = reader.Seek(0, sdkio.SeekCurrent) // Get the Bodies current offset. | |
263 | r.ResetBody() | |
264 | } | |
265 | ||
266 | // Presign returns the request's signed URL. Error will be returned | |
267 | // if the signing fails. The expire parameter is only used for presigned Amazon | |
268 | // S3 API requests. All other AWS services will use a fixed expiration | |
269 | // time of 15 minutes. | |
270 | // | |
271 | // It is invalid to create a presigned URL with a expire duration 0 or less. An | |
272 | // error is returned if expire duration is 0 or less. | |
273 | func (r *Request) Presign(expire time.Duration) (string, error) { | |
274 | r = r.copy() | |
275 | ||
276 | // Presign requires all headers be hoisted. There is no way to retrieve | |
277 | // the signed headers not hoisted without this. Making the presigned URL | |
278 | // useless. | |
279 | r.NotHoist = false | |
280 | ||
281 | u, _, err := getPresignedURL(r, expire) | |
282 | return u, err | |
283 | } | |
284 | ||
285 | // PresignRequest behaves just like presign, with the addition of returning a | |
286 | // set of headers that were signed. The expire parameter is only used for | |
287 | // presigned Amazon S3 API requests. All other AWS services will use a fixed | |
288 | // expiration time of 15 minutes. | |
289 | // | |
290 | // It is invalid to create a presigned URL with a expire duration 0 or less. An | |
291 | // error is returned if expire duration is 0 or less. | |
292 | // | |
293 | // Returns the URL string for the API operation with signature in the query string, | |
294 | // and the HTTP headers that were included in the signature. These headers must | |
295 | // be included in any HTTP request made with the presigned URL. | |
296 | // | |
297 | // To prevent hoisting any headers to the query string set NotHoist to true on | |
298 | // this Request value prior to calling PresignRequest. | |
299 | func (r *Request) PresignRequest(expire time.Duration) (string, http.Header, error) { | |
300 | r = r.copy() | |
301 | return getPresignedURL(r, expire) | |
302 | } | |
303 | ||
304 | // IsPresigned returns true if the request represents a presigned API url. | |
305 | func (r *Request) IsPresigned() bool { | |
306 | return r.ExpireTime != 0 | |
307 | } | |
308 | ||
309 | func getPresignedURL(r *Request, expire time.Duration) (string, http.Header, error) { | |
310 | if expire <= 0 { | |
311 | return "", nil, awserr.New( | |
312 | ErrCodeInvalidPresignExpire, | |
313 | "presigned URL requires an expire duration greater than 0", | |
314 | nil, | |
315 | ) | |
316 | } | |
317 | ||
318 | r.ExpireTime = expire | |
319 | ||
320 | if r.Operation.BeforePresignFn != nil { | |
321 | if err := r.Operation.BeforePresignFn(r); err != nil { | |
322 | return "", nil, err | |
323 | } | |
324 | } | |
325 | ||
326 | if err := r.Sign(); err != nil { | |
327 | return "", nil, err | |
328 | } | |
329 | ||
330 | return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil | |
331 | } | |
332 | ||
333 | func debugLogReqError(r *Request, stage string, retrying bool, err error) { | |
334 | if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) { | |
335 | return | |
336 | } | |
337 | ||
338 | retryStr := "not retrying" | |
339 | if retrying { | |
340 | retryStr = "will retry" | |
341 | } | |
342 | ||
343 | r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v", | |
344 | stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err)) | |
345 | } | |
346 | ||
347 | // Build will build the request's object so it can be signed and sent | |
348 | // to the service. Build will also validate all the request's parameters. | |
349 | // Any additional build Handlers set on this request will be run | |
350 | // in the order they were set. | |
351 | // | |
352 | // The request will only be built once. Multiple calls to build will have | |
353 | // no effect. | |
354 | // | |
355 | // If any Validate or Build errors occur the build will stop and the error | |
356 | // which occurred will be returned. | |
357 | func (r *Request) Build() error { | |
358 | if !r.built { | |
359 | r.Handlers.Validate.Run(r) | |
360 | if r.Error != nil { | |
361 | debugLogReqError(r, "Validate Request", false, r.Error) | |
362 | return r.Error | |
363 | } | |
364 | r.Handlers.Build.Run(r) | |
365 | if r.Error != nil { | |
366 | debugLogReqError(r, "Build Request", false, r.Error) | |
367 | return r.Error | |
368 | } | |
369 | r.built = true | |
370 | } | |
371 | ||
372 | return r.Error | |
373 | } | |
374 | ||
375 | // Sign will sign the request, returning error if errors are encountered. | |
376 | // | |
377 | // Sign will build the request prior to signing. All Sign Handlers will | |
378 | // be executed in the order they were set. | |
379 | func (r *Request) Sign() error { | |
380 | r.Build() | |
381 | if r.Error != nil { | |
382 | debugLogReqError(r, "Build Request", false, r.Error) | |
383 | return r.Error | |
384 | } | |
385 | ||
386 | r.Handlers.Sign.Run(r) | |
387 | return r.Error | |
388 | } | |
389 | ||
390 | func (r *Request) getNextRequestBody() (io.ReadCloser, error) { | |
391 | if r.safeBody != nil { | |
392 | r.safeBody.Close() | |
393 | } | |
394 | ||
395 | r.safeBody = newOffsetReader(r.Body, r.BodyStart) | |
396 | ||
397 | // Go 1.8 tightened and clarified the rules code needs to use when building | |
398 | // requests with the http package. Go 1.8 removed the automatic detection | |
399 | // of if the Request.Body was empty, or actually had bytes in it. The SDK | |
400 | // always sets the Request.Body even if it is empty and should not actually | |
401 | // be sent. This is incorrect. | |
402 | // | |
403 | // Go 1.8 did add a http.NoBody value that the SDK can use to tell the http | |
404 | // client that the request really should be sent without a body. The | |
405 | // Request.Body cannot be set to nil, which is preferable, because the | |
406 | // field is exported and could introduce nil pointer dereferences for users | |
407 | // of the SDK if they used that field. | |
408 | // | |
409 | // Related golang/go#18257 | |
410 | l, err := aws.SeekerLen(r.Body) | |
411 | if err != nil { | |
412 | return nil, awserr.New(ErrCodeSerialization, "failed to compute request body size", err) | |
413 | } | |
414 | ||
415 | var body io.ReadCloser | |
416 | if l == 0 { | |
417 | body = NoBody | |
418 | } else if l > 0 { | |
419 | body = r.safeBody | |
420 | } else { | |
421 | // Hack to prevent sending bodies for methods where the body | |
422 | // should be ignored by the server. Sending bodies on these | |
423 | // methods without an associated ContentLength will cause the | |
424 | // request to socket timeout because the server does not handle | |
425 | // Transfer-Encoding: chunked bodies for these methods. | |
426 | // | |
427 | // This would only happen if a aws.ReaderSeekerCloser was used with | |
428 | // a io.Reader that was not also an io.Seeker, or did not implement | |
429 | // Len() method. | |
430 | switch r.Operation.HTTPMethod { | |
431 | case "GET", "HEAD", "DELETE": | |
432 | body = NoBody | |
433 | default: | |
434 | body = r.safeBody | |
435 | } | |
436 | } | |
437 | ||
438 | return body, nil | |
439 | } | |
440 | ||
441 | // GetBody will return an io.ReadSeeker of the Request's underlying | |
442 | // input body with a concurrency safe wrapper. | |
443 | func (r *Request) GetBody() io.ReadSeeker { | |
444 | return r.safeBody | |
445 | } | |
446 | ||
447 | // Send will send the request, returning error if errors are encountered. | |
448 | // | |
449 | // Send will sign the request prior to sending. All Send Handlers will | |
450 | // be executed in the order they were set. | |
451 | // | |
452 | // Canceling a request is non-deterministic. If a request has been canceled, | |
453 | // then the transport will choose, randomly, one of the state channels during | |
454 | // reads or getting the connection. | |
455 | // | |
456 | // readLoop() and getConn(req *Request, cm connectMethod) | |
457 | // https://github.com/golang/go/blob/master/src/net/http/transport.go | |
458 | // | |
459 | // Send will not close the request.Request's body. | |
460 | func (r *Request) Send() error { | |
461 | defer func() { | |
462 | // Regardless of success or failure of the request trigger the Complete | |
463 | // request handlers. | |
464 | r.Handlers.Complete.Run(r) | |
465 | }() | |
466 | ||
467 | if err := r.Error; err != nil { | |
468 | return err | |
469 | } | |
470 | ||
471 | for { | |
472 | r.Error = nil | |
473 | r.AttemptTime = time.Now() | |
474 | ||
475 | if err := r.Sign(); err != nil { | |
476 | debugLogReqError(r, "Sign Request", false, err) | |
477 | return err | |
478 | } | |
479 | ||
480 | if err := r.sendRequest(); err == nil { | |
481 | return nil | |
482 | } else if !shouldRetryCancel(r.Error) { | |
483 | return err | |
484 | } else { | |
485 | r.Handlers.Retry.Run(r) | |
486 | r.Handlers.AfterRetry.Run(r) | |
487 | ||
488 | if r.Error != nil || !aws.BoolValue(r.Retryable) { | |
489 | return r.Error | |
490 | } | |
491 | ||
492 | r.prepareRetry() | |
493 | continue | |
494 | } | |
495 | } | |
496 | } | |
497 | ||
498 | func (r *Request) prepareRetry() { | |
499 | if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) { | |
500 | r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d", | |
501 | r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount)) | |
502 | } | |
503 | ||
504 | // The previous http.Request will have a reference to the r.Body | |
505 | // and the HTTP Client's Transport may still be reading from | |
506 | // the request's body even though the Client's Do returned. | |
507 | r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil) | |
508 | r.ResetBody() | |
509 | ||
510 | // Closing response body to ensure that no response body is leaked | |
511 | // between retry attempts. | |
512 | if r.HTTPResponse != nil && r.HTTPResponse.Body != nil { | |
513 | r.HTTPResponse.Body.Close() | |
514 | } | |
515 | } | |
516 | ||
517 | func (r *Request) sendRequest() (sendErr error) { | |
518 | defer r.Handlers.CompleteAttempt.Run(r) | |
519 | ||
520 | r.Retryable = nil | |
521 | r.Handlers.Send.Run(r) | |
522 | if r.Error != nil { | |
523 | debugLogReqError(r, "Send Request", r.WillRetry(), r.Error) | |
524 | return r.Error | |
525 | } | |
526 | ||
527 | r.Handlers.UnmarshalMeta.Run(r) | |
528 | r.Handlers.ValidateResponse.Run(r) | |
529 | if r.Error != nil { | |
530 | r.Handlers.UnmarshalError.Run(r) | |
531 | debugLogReqError(r, "Validate Response", r.WillRetry(), r.Error) | |
532 | return r.Error | |
533 | } | |
534 | ||
535 | r.Handlers.Unmarshal.Run(r) | |
536 | if r.Error != nil { | |
537 | debugLogReqError(r, "Unmarshal Response", r.WillRetry(), r.Error) | |
538 | return r.Error | |
539 | } | |
540 | ||
541 | return nil | |
542 | } | |
543 | ||
544 | // copy will copy a request which will allow for local manipulation of the | |
545 | // request. | |
546 | func (r *Request) copy() *Request { | |
547 | req := &Request{} | |
548 | *req = *r | |
549 | req.Handlers = r.Handlers.Copy() | |
550 | op := *r.Operation | |
551 | req.Operation = &op | |
552 | return req | |
553 | } | |
554 | ||
555 | // AddToUserAgent adds the string to the end of the request's current user agent. | |
556 | func AddToUserAgent(r *Request, s string) { | |
557 | curUA := r.HTTPRequest.Header.Get("User-Agent") | |
558 | if len(curUA) > 0 { | |
559 | s = curUA + " " + s | |
560 | } | |
561 | r.HTTPRequest.Header.Set("User-Agent", s) | |
562 | } | |
563 | ||
564 | type temporary interface { | |
565 | Temporary() bool | |
566 | } | |
567 | ||
568 | func shouldRetryCancel(err error) bool { | |
569 | switch err := err.(type) { | |
570 | case awserr.Error: | |
571 | if err.Code() == CanceledErrorCode { | |
572 | return false | |
573 | } | |
574 | return shouldRetryCancel(err.OrigErr()) | |
575 | case *url.Error: | |
576 | if strings.Contains(err.Error(), "connection refused") { | |
577 | // Refused connections should be retried as the service may not yet | |
578 | // be running on the port. Go TCP dial considers refused | |
579 | // connections as not temporary. | |
580 | return true | |
581 | } | |
582 | // *url.Error only implements Temporary after golang 1.6 but since | |
583 | // url.Error only wraps the error: | |
584 | return shouldRetryCancel(err.Err) | |
585 | case temporary: | |
586 | // If the error is temporary, we want to allow continuation of the | |
587 | // retry process | |
588 | return err.Temporary() | |
589 | case nil: | |
590 | // `awserr.Error.OrigErr()` can be nil, meaning there was an error but | |
591 | // because we don't know the cause, it is marked as retriable. See | |
592 | // TestRequest4xxUnretryable for an example. | |
593 | return true | |
594 | default: | |
595 | switch err.Error() { | |
596 | case "net/http: request canceled", | |
597 | "net/http: request canceled while waiting for connection": | |
598 | // known 1.5 error case when an http request is cancelled | |
599 | return false | |
600 | } | |
601 | // here we don't know the error; so we allow a retry. | |
602 | return true | |
603 | } | |
604 | } | |
605 | ||
606 | // SanitizeHostForHeader removes default port from host and updates request.Host | |
607 | func SanitizeHostForHeader(r *http.Request) { | |
608 | host := getHost(r) | |
609 | port := portOnly(host) | |
610 | if port != "" && isDefaultPort(r.URL.Scheme, port) { | |
611 | r.Host = stripPort(host) | |
612 | } | |
613 | } | |
614 | ||
615 | // Returns host from request | |
616 | func getHost(r *http.Request) string { | |
617 | if r.Host != "" { | |
618 | return r.Host | |
619 | } | |
620 | ||
621 | return r.URL.Host | |
622 | } | |
623 | ||
624 | // Hostname returns u.Host, without any port number. | |
625 | // | |
626 | // If Host is an IPv6 literal with a port number, Hostname returns the | |
627 | // IPv6 literal without the square brackets. IPv6 literals may include | |
628 | // a zone identifier. | |
629 | // | |
630 | // Copied from the Go 1.8 standard library (net/url) | |
631 | func stripPort(hostport string) string { | |
632 | colon := strings.IndexByte(hostport, ':') | |
633 | if colon == -1 { | |
634 | return hostport | |
635 | } | |
636 | if i := strings.IndexByte(hostport, ']'); i != -1 { | |
637 | return strings.TrimPrefix(hostport[:i], "[") | |
638 | } | |
639 | return hostport[:colon] | |
640 | } | |
641 | ||
642 | // Port returns the port part of u.Host, without the leading colon. | |
643 | // If u.Host doesn't contain a port, Port returns an empty string. | |
644 | // | |
645 | // Copied from the Go 1.8 standard library (net/url) | |
646 | func portOnly(hostport string) string { | |
647 | colon := strings.IndexByte(hostport, ':') | |
648 | if colon == -1 { | |
649 | return "" | |
650 | } | |
651 | if i := strings.Index(hostport, "]:"); i != -1 { | |
652 | return hostport[i+len("]:"):] | |
653 | } | |
654 | if strings.Contains(hostport, "]") { | |
655 | return "" | |
656 | } | |
657 | return hostport[colon+len(":"):] | |
658 | } | |
659 | ||
660 | // Returns true if the specified URI is using the standard port | |
661 | // (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs) | |
662 | func isDefaultPort(scheme, port string) bool { | |
663 | if port == "" { | |
664 | return true | |
665 | } | |
666 | ||
667 | lowerCaseScheme := strings.ToLower(scheme) | |
668 | if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") { | |
669 | return true | |
670 | } | |
671 | ||
672 | return false | |
673 | } |