in utils/httputil/httputil.go [283:351]
func Send(method, rawurl string, options ...SendOption) (*http.Response, error) {
u, err := url.Parse(rawurl)
if err != nil {
return nil, fmt.Errorf("parse url: %s", err)
}
opts := &sendOptions{
body: nil,
timeout: 60 * time.Second,
acceptedCodes: map[int]bool{http.StatusOK: true},
headers: map[string]string{},
retry: retryOptions{backoff: &backoff.StopBackOff{}},
transport: nil, // Use HTTP default.
ctx: context.Background(),
url: u,
httpFallbackDisabled: true,
}
for _, o := range options {
o(opts)
}
req, err := newRequest(method, opts)
if err != nil {
return nil, err
}
client := &http.Client{
Timeout: opts.timeout,
CheckRedirect: opts.redirect,
Transport: opts.transport,
}
var resp *http.Response
for {
resp, err = client.Do(req)
// Retry without tls. During migration there would be a time when the
// component receiving the tls request does not serve https response.
// TODO (@evelynl): disable retry after tls migration.
if err != nil && req.URL.Scheme == "https" && !opts.httpFallbackDisabled {
originalErr := err
resp, err = fallbackToHTTP(client, method, opts)
if err != nil {
// Sometimes the request fails for a reason unrelated to https.
// To keep this reason visible, we always include the original
// error.
err = fmt.Errorf(
"failed to fallback https to http, original https error: %s,\n"+
"fallback http error: %s", originalErr, err)
}
}
if err != nil ||
(isRetryable(resp.StatusCode) && !opts.acceptedCodes[resp.StatusCode]) ||
(opts.retry.extraCodes[resp.StatusCode]) {
d := opts.retry.backoff.NextBackOff()
if d == backoff.Stop {
break // Backoff timed out.
}
time.Sleep(d)
continue
}
break
}
if err != nil {
return nil, NetworkError{err}
}
if !opts.acceptedCodes[resp.StatusCode] {
return nil, NewStatusError(resp)
}
return resp, nil
}