18
18
package fetcher
19
19
20
20
import (
21
+ "context"
21
22
"fmt"
22
23
"io"
23
24
"net/http"
24
25
"strconv"
26
+ "time"
25
27
28
+ "github.com/cenkalti/backoff/v5"
26
29
"github.com/theupdateframework/go-tuf/v2/metadata"
27
30
)
28
31
@@ -39,11 +42,11 @@ type Fetcher interface {
39
42
40
43
// DefaultFetcher implements Fetcher
41
44
type DefaultFetcher struct {
45
+ // httpClient configuration
42
46
httpUserAgent string
43
47
client httpClient
44
- // for implementation of retry logic in a future pull request
45
- // retryInterval time.Duration
46
- // retryCount int
48
+ // retry logic configuration
49
+ retryOptions []backoff.RetryOption
47
50
}
48
51
49
52
func (d * DefaultFetcher ) SetHTTPUserAgent (httpUserAgent string ) {
@@ -61,48 +64,59 @@ func (d *DefaultFetcher) DownloadFile(urlPath string, maxLength int64) ([]byte,
61
64
if d .httpUserAgent != "" {
62
65
req .Header .Set ("User-Agent" , d .httpUserAgent )
63
66
}
64
- // Execute the request.
65
- res , err := d .client .Do (req )
66
- if err != nil {
67
- return nil , err
68
- }
69
- defer res .Body .Close ()
70
- // Handle HTTP status codes.
71
- if res .StatusCode != http .StatusOK {
72
- return nil , & metadata.ErrDownloadHTTP {StatusCode : res .StatusCode , URL : urlPath }
73
- }
74
- var length int64
75
- // Get content length from header (might not be accurate, -1 or not set).
76
- if header := res .Header .Get ("Content-Length" ); header != "" {
77
- length , err = strconv .ParseInt (header , 10 , 0 )
67
+
68
+ operation := func () ([]byte , error ) {
69
+ // Execute the request.
70
+ res , err := d .client .Do (req )
71
+ if err != nil {
72
+ return nil , err
73
+ }
74
+ defer res .Body .Close ()
75
+ // Handle HTTP status codes.
76
+ if res .StatusCode != http .StatusOK {
77
+ return nil , & metadata.ErrDownloadHTTP {StatusCode : res .StatusCode , URL : urlPath }
78
+ }
79
+ var length int64
80
+ // Get content length from header (might not be accurate, -1 or not set).
81
+ if header := res .Header .Get ("Content-Length" ); header != "" {
82
+ length , err = strconv .ParseInt (header , 10 , 0 )
83
+ if err != nil {
84
+ return nil , err
85
+ }
86
+ // Error if the reported size is greater than what is expected.
87
+ if length > maxLength {
88
+ return nil , & metadata.ErrDownloadLengthMismatch {Msg : fmt .Sprintf ("download failed for %s, length %d is larger than expected %d" , urlPath , length , maxLength )}
89
+ }
90
+ }
91
+ // Although the size has been checked above, use a LimitReader in case
92
+ // the reported size is inaccurate, or size is -1 which indicates an
93
+ // unknown length. We read maxLength + 1 in order to check if the read data
94
+ // surpassed our set limit.
95
+ data , err := io .ReadAll (io .LimitReader (res .Body , maxLength + 1 ))
78
96
if err != nil {
79
97
return nil , err
80
98
}
81
99
// Error if the reported size is greater than what is expected.
100
+ length = int64 (len (data ))
82
101
if length > maxLength {
83
102
return nil , & metadata.ErrDownloadLengthMismatch {Msg : fmt .Sprintf ("download failed for %s, length %d is larger than expected %d" , urlPath , length , maxLength )}
84
103
}
104
+
105
+ return data , nil
85
106
}
86
- // Although the size has been checked above, use a LimitReader in case
87
- // the reported size is inaccurate, or size is -1 which indicates an
88
- // unknown length. We read maxLength + 1 in order to check if the read data
89
- // surpassed our set limit.
90
- data , err := io .ReadAll (io .LimitReader (res .Body , maxLength + 1 ))
107
+ data , err := backoff .Retry (context .Background (), operation , d .retryOptions ... )
91
108
if err != nil {
92
109
return nil , err
93
110
}
94
- // Error if the reported size is greater than what is expected.
95
- length = int64 (len (data ))
96
- if length > maxLength {
97
- return nil , & metadata.ErrDownloadLengthMismatch {Msg : fmt .Sprintf ("download failed for %s, length %d is larger than expected %d" , urlPath , length , maxLength )}
98
- }
99
111
100
112
return data , nil
101
113
}
102
114
103
115
func NewDefaultFetcher () * DefaultFetcher {
104
116
return & DefaultFetcher {
105
117
client : http .DefaultClient ,
118
+ // default to attempting the HTTP request once
119
+ retryOptions : []backoff.RetryOption {backoff .WithMaxTries (1 )},
106
120
}
107
121
}
108
122
@@ -136,3 +150,13 @@ func (f *DefaultFetcher) SetTransport(rt http.RoundTripper) error {
136
150
f .client = hc
137
151
return nil
138
152
}
153
+
154
+ func (f * DefaultFetcher ) SetRetry (retryInterval time.Duration , retryCount uint ) {
155
+ constantBackOff := backoff .WithBackOff (backoff .NewConstantBackOff (retryInterval ))
156
+ maxTryCount := backoff .WithMaxTries (retryCount )
157
+ f .SetRetryOptions (constantBackOff , maxTryCount )
158
+ }
159
+
160
+ func (f * DefaultFetcher ) SetRetryOptions (retryOptions ... backoff.RetryOption ) {
161
+ f .retryOptions = retryOptions
162
+ }
0 commit comments