1
1
package net
2
2
3
3
import (
4
+ "bytes"
4
5
"crypto/tls"
6
+ "errors"
5
7
"fmt"
6
8
"io"
7
9
"net/http"
@@ -14,6 +16,7 @@ import (
14
16
15
17
"github.com/opentracing/opentracing-go"
16
18
"github.com/opentracing/opentracing-go/ext"
19
+ skpio "github.com/zalando/skipper/io"
17
20
"github.com/zalando/skipper/logging"
18
21
"github.com/zalando/skipper/secrets"
19
22
)
@@ -23,15 +26,18 @@ const (
23
26
defaultRefreshInterval = 5 * time .Minute
24
27
)
25
28
29
+ var errRequestNotFound = errors .New ("request not found" )
30
+
26
31
// Client adds additional features like Bearer token injection, and
27
32
// opentracing to the wrapped http.Client with the same interface as
28
33
// http.Client from the stdlib.
29
34
type Client struct {
30
- once sync.Once
31
- client http.Client
32
- tr * Transport
33
- log logging.Logger
34
- sr secrets.SecretsReader
35
+ once sync.Once
36
+ client http.Client
37
+ tr * Transport
38
+ log logging.Logger
39
+ sr secrets.SecretsReader
40
+ retryBuffers * sync.Map
35
41
}
36
42
37
43
// NewClient creates a wrapped http.Client and uses Transport to
@@ -67,9 +73,10 @@ func NewClient(o Options) *Client {
67
73
Transport : tr ,
68
74
CheckRedirect : o .CheckRedirect ,
69
75
},
70
- tr : tr ,
71
- log : o .Log ,
72
- sr : sr ,
76
+ tr : tr ,
77
+ log : o .Log ,
78
+ sr : sr ,
79
+ retryBuffers : & sync.Map {},
73
80
}
74
81
75
82
return c
@@ -125,9 +132,33 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
125
132
req .Header .Set ("Authorization" , "Bearer " + string (b ))
126
133
}
127
134
}
135
+ if req .Body != nil && req .Body != http .NoBody && req .ContentLength > 0 {
136
+ retryBuffer := skpio .NewCopyBodyStream (int (req .ContentLength ), & bytes.Buffer {}, req .Body )
137
+ c .retryBuffers .Store (req , retryBuffer )
138
+ req .Body = retryBuffer
139
+ }
128
140
return c .client .Do (req )
129
141
}
130
142
143
+ func (c * Client ) Retry (req * http.Request ) (* http.Response , error ) {
144
+ if req .Body == nil || req .Body == http .NoBody {
145
+ return c .Do (req )
146
+ }
147
+
148
+ buf , ok := c .retryBuffers .LoadAndDelete (req )
149
+ if ! ok {
150
+ return nil , fmt .Errorf ("no retry possible, %w: %s %s" , errRequestNotFound , req .Method , req .URL )
151
+ }
152
+
153
+ retryBuffer , ok := buf .(* skpio.CopyBodyStream )
154
+ if ! ok {
155
+ return nil , fmt .Errorf ("no retry possible, no retry buffer for request: %s %s" , req .Method , req .URL )
156
+ }
157
+ req .Body = retryBuffer .GetBody ()
158
+
159
+ return c .Do (req )
160
+ }
161
+
131
162
// CloseIdleConnections delegates the call to the underlying
132
163
// http.Client.
133
164
func (c * Client ) CloseIdleConnections () {
0 commit comments