Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/registry-facade/pkg/registry/http_client.go
2499 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package registry
6
7
import (
8
"context"
9
"net/http"
10
11
"github.com/gitpod-io/gitpod/common-go/log"
12
"github.com/hashicorp/go-retryablehttp"
13
"github.com/sirupsen/logrus"
14
)
15
16
// Temporaryable is to match an error that has `.Temporary()`
17
type Temporaryable interface {
18
Temporary() bool
19
}
20
21
// Timeoutable is to match an error that has `.Timeout()`
22
type Timeoutable interface {
23
Timeout() bool
24
}
25
26
type Option func(opts *httpOpts)
27
28
func NewRetryableHTTPClient(options ...Option) *http.Client {
29
opts := defaultOptions()
30
for _, o := range options {
31
o(&opts)
32
}
33
34
client := retryablehttp.NewClient()
35
client.RetryMax = opts.RetryMax
36
client.Logger = opts.Logger
37
client.RequestLogHook = opts.RequestLogHook
38
client.ResponseLogHook = opts.ResponseLogHook
39
40
if opts.HTTPClient != nil {
41
client.HTTPClient = opts.HTTPClient
42
}
43
44
client.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
45
if terr, ok := err.(Temporaryable); ok && terr.Temporary() {
46
return true, nil
47
}
48
49
if terr, ok := err.(Timeoutable); ok && terr.Timeout() {
50
return true, nil
51
}
52
53
return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
54
}
55
56
return client.StandardClient()
57
}
58
59
func defaultOptions() httpOpts {
60
return httpOpts{
61
RetryMax: 5,
62
Logger: retryablehttp.LeveledLogger(&leveledLogrus{log.Log}),
63
64
RequestLogHook: func(logger retryablehttp.Logger, req *http.Request, attempt int) {
65
if attempt > 0 {
66
log.Warnf("%v %v request failed. Retry count: %v", req.Method, req.URL, attempt)
67
}
68
},
69
}
70
}
71
72
type httpOpts struct {
73
HTTPClient *http.Client
74
Logger retryablehttp.LeveledLogger
75
76
RetryMax int
77
78
RequestLogHook retryablehttp.RequestLogHook
79
80
ResponseLogHook retryablehttp.ResponseLogHook
81
}
82
83
// WithRequestLogHook can be used to configure a custom request log hook.
84
func WithRequestLogHook(hook retryablehttp.RequestLogHook) Option {
85
return func(opts *httpOpts) {
86
opts.RequestLogHook = hook
87
}
88
}
89
90
// WithResponseLogHook can be used to configure a custom response log hook.
91
func WithResponseLogHook(hook retryablehttp.ResponseLogHook) Option {
92
return func(opts *httpOpts) {
93
opts.ResponseLogHook = hook
94
}
95
}
96
97
// WithRetryMax can be used to configure a custom number of retries.
98
func WithRetryMax(retryMax int) Option {
99
return func(opts *httpOpts) {
100
opts.RetryMax = retryMax
101
}
102
}
103
104
func WithHTTPClient(client *http.Client) Option {
105
return func(opts *httpOpts) {
106
opts.HTTPClient = client
107
}
108
}
109
110
type leveledLogrus struct {
111
*logrus.Entry
112
}
113
114
func (l *leveledLogrus) fields(keysAndValues ...interface{}) map[string]interface{} {
115
fields := make(map[string]interface{})
116
117
for i := 0; i < len(keysAndValues)-1; i += 2 {
118
fields[keysAndValues[i].(string)] = keysAndValues[i+1]
119
}
120
121
return fields
122
}
123
124
func (l *leveledLogrus) Error(msg string, keysAndValues ...interface{}) {
125
l.WithFields(l.fields(keysAndValues...)).Error(msg)
126
}
127
128
func (l *leveledLogrus) Info(msg string, keysAndValues ...interface{}) {
129
l.WithFields(l.fields(keysAndValues...)).Info(msg)
130
}
131
132
func (l *leveledLogrus) Debug(msg string, keysAndValues ...interface{}) {
133
l.WithFields(l.fields(keysAndValues...)).Debug(msg)
134
}
135
136
func (l *leveledLogrus) Warn(msg string, keysAndValues ...interface{}) {
137
l.WithFields(l.fields(keysAndValues...)).Warn(msg)
138
}
139
140