runtime/http_client.go (94 lines of code) (raw):
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package zanzibar
import (
"context"
"net/http"
"strconv"
"time"
"github.com/uber-go/tally"
"github.com/uber/zanzibar/runtime/jsonwrapper"
)
// CheckRetry specifies a policy for handling retries. It is called
// following each request with the response and error values returned by
// the http.Client. If CheckRetry returns false, the Client stops retrying
// and returns the response to the caller. If CheckRetry returns an error,
// that error value is returned in lieu of the error from the request.
type CheckRetry func(ctx context.Context, timeoutAndRetryOptions *TimeoutAndRetryOptions, resp *http.Response, err error) bool
// HTTPClient defines a http client.
type HTTPClient struct {
Client *http.Client
BaseURL string
DefaultHeaders map[string]string
JSONWrapper jsonwrapper.JSONWrapper
ContextLogger ContextLogger
contextMetrics ContextMetrics
CheckRetry CheckRetry
}
// UnexpectedHTTPError defines an error for HTTP
type UnexpectedHTTPError struct {
StatusCode int
RawBody []byte
}
func (rawErr *UnexpectedHTTPError) Error() string {
return "Unexpected http client response (" +
strconv.Itoa(rawErr.StatusCode) + ")"
}
// NewHTTPClient is deprecated, use NewHTTPClientContext instead
func NewHTTPClient(
contextLogger ContextLogger,
scope tally.Scope,
jsonWrapper jsonwrapper.JSONWrapper,
clientID string,
methodToTargetEndpoint map[string]string,
baseURL string,
defaultHeaders map[string]string,
timeout time.Duration,
) *HTTPClient {
return NewHTTPClientContext(
contextLogger,
NewContextMetrics(scope),
jsonWrapper,
clientID,
methodToTargetEndpoint,
baseURL,
defaultHeaders,
timeout,
true,
)
}
// NewHTTPClientContext will allocate a http client.
func NewHTTPClientContext(
contextLogger ContextLogger,
ContextMetrics ContextMetrics,
jsonWrapper jsonwrapper.JSONWrapper,
clientID string,
methodToTargetEndpoint map[string]string,
baseURL string,
defaultHeaders map[string]string,
timeout time.Duration,
followRedirect bool,
) *HTTPClient {
var checkRedirect func(req *http.Request, via []*http.Request) error
if !followRedirect {
checkRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
return &HTTPClient{
Client: &http.Client{
Transport: &http.Transport{
DisableKeepAlives: false,
MaxIdleConns: 500,
MaxIdleConnsPerHost: 500,
},
Timeout: timeout,
CheckRedirect: checkRedirect,
},
BaseURL: baseURL,
DefaultHeaders: defaultHeaders,
ContextLogger: contextLogger,
contextMetrics: ContextMetrics,
JSONWrapper: jsonWrapper,
CheckRetry: DefaultRetryPolicy,
}
}
// DefaultRetryPolicy allows retries for any type of server error
func DefaultRetryPolicy(ctx context.Context, timeoutAndRetryOptions *TimeoutAndRetryOptions, resp *http.Response, err error) bool {
// do not retry on context.Canceled or context.DeadlineExceeded
if ctx.Err() != nil {
return false
}
//wait for the backoff time
timer := time.NewTimer(timeoutAndRetryOptions.BackOffTimeAcrossRetriesInMs)
select {
case <-timer.C:
}
return true
}