Path: blob/main/components/registry-facade/pkg/registry/http_client.go
2499 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package registry56import (7"context"8"net/http"910"github.com/gitpod-io/gitpod/common-go/log"11"github.com/hashicorp/go-retryablehttp"12"github.com/sirupsen/logrus"13)1415// Temporaryable is to match an error that has `.Temporary()`16type Temporaryable interface {17Temporary() bool18}1920// Timeoutable is to match an error that has `.Timeout()`21type Timeoutable interface {22Timeout() bool23}2425type Option func(opts *httpOpts)2627func NewRetryableHTTPClient(options ...Option) *http.Client {28opts := defaultOptions()29for _, o := range options {30o(&opts)31}3233client := retryablehttp.NewClient()34client.RetryMax = opts.RetryMax35client.Logger = opts.Logger36client.RequestLogHook = opts.RequestLogHook37client.ResponseLogHook = opts.ResponseLogHook3839if opts.HTTPClient != nil {40client.HTTPClient = opts.HTTPClient41}4243client.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {44if terr, ok := err.(Temporaryable); ok && terr.Temporary() {45return true, nil46}4748if terr, ok := err.(Timeoutable); ok && terr.Timeout() {49return true, nil50}5152return retryablehttp.DefaultRetryPolicy(ctx, resp, err)53}5455return client.StandardClient()56}5758func defaultOptions() httpOpts {59return httpOpts{60RetryMax: 5,61Logger: retryablehttp.LeveledLogger(&leveledLogrus{log.Log}),6263RequestLogHook: func(logger retryablehttp.Logger, req *http.Request, attempt int) {64if attempt > 0 {65log.Warnf("%v %v request failed. Retry count: %v", req.Method, req.URL, attempt)66}67},68}69}7071type httpOpts struct {72HTTPClient *http.Client73Logger retryablehttp.LeveledLogger7475RetryMax int7677RequestLogHook retryablehttp.RequestLogHook7879ResponseLogHook retryablehttp.ResponseLogHook80}8182// WithRequestLogHook can be used to configure a custom request log hook.83func WithRequestLogHook(hook retryablehttp.RequestLogHook) Option {84return func(opts *httpOpts) {85opts.RequestLogHook = hook86}87}8889// WithResponseLogHook can be used to configure a custom response log hook.90func WithResponseLogHook(hook retryablehttp.ResponseLogHook) Option {91return func(opts *httpOpts) {92opts.ResponseLogHook = hook93}94}9596// WithRetryMax can be used to configure a custom number of retries.97func WithRetryMax(retryMax int) Option {98return func(opts *httpOpts) {99opts.RetryMax = retryMax100}101}102103func WithHTTPClient(client *http.Client) Option {104return func(opts *httpOpts) {105opts.HTTPClient = client106}107}108109type leveledLogrus struct {110*logrus.Entry111}112113func (l *leveledLogrus) fields(keysAndValues ...interface{}) map[string]interface{} {114fields := make(map[string]interface{})115116for i := 0; i < len(keysAndValues)-1; i += 2 {117fields[keysAndValues[i].(string)] = keysAndValues[i+1]118}119120return fields121}122123func (l *leveledLogrus) Error(msg string, keysAndValues ...interface{}) {124l.WithFields(l.fields(keysAndValues...)).Error(msg)125}126127func (l *leveledLogrus) Info(msg string, keysAndValues ...interface{}) {128l.WithFields(l.fields(keysAndValues...)).Info(msg)129}130131func (l *leveledLogrus) Debug(msg string, keysAndValues ...interface{}) {132l.WithFields(l.fields(keysAndValues...)).Debug(msg)133}134135func (l *leveledLogrus) Warn(msg string, keysAndValues ...interface{}) {136l.WithFields(l.fields(keysAndValues...)).Warn(msg)137}138139140