Skip to content

Commit b0532f5

Browse files
author
jojoliang
committed
add retry options
1 parent 93ba1b1 commit b0532f5

File tree

5 files changed

+169
-2
lines changed

5 files changed

+169
-2
lines changed

auth.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var ciParameters = map[string]bool{
6565
"imageview2/": true,
6666
}
6767

68+
// 非线程安全,只能在进程初始化(而不是Client初始化)时做设置
6869
func SetNeedSignHeaders(key string, val bool) {
6970
NeedSignHeaders[key] = val
7071
}

auth_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package cos
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
7+
"net/http/httptest"
8+
"strconv"
9+
"strings"
610
"testing"
711
"time"
812
)
@@ -52,3 +56,70 @@ func TestAuthorizationTransport(t *testing.T) {
5256
req, _ := http.NewRequest("GET", client.BaseURL.BucketURL.String(), nil)
5357
client.doAPI(context.Background(), req, nil, true)
5458
}
59+
60+
func TestCVMCredentialsTransport(t *testing.T) {
61+
setup()
62+
defer teardown()
63+
uri := client.BaseURL.BucketURL.String()
64+
ak := "test_ak"
65+
sk := "test_sk"
66+
token := "test_token"
67+
68+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
69+
if r.Header.Get("x-cos-security-token") != token {
70+
t.Errorf("CVMCredentialsTransport x-cos-security-token error, want:%v, return:%v\n", token, r.Header.Get("x-cos-security-token"))
71+
}
72+
auth := r.Header.Get("Authorization")
73+
if auth == "" {
74+
t.Error("CVMCredentialsTransport didn't add Authorization header")
75+
}
76+
field := strings.Split(auth, "&")
77+
if len(field) != 7 {
78+
t.Errorf("CVMCredentialsTransport Authorization header format error: %v\n", auth)
79+
}
80+
st_et := strings.Split(strings.Split(field[2], "=")[1], ";")
81+
st, _ := strconv.ParseInt(st_et[0], 10, 64)
82+
et, _ := strconv.ParseInt(st_et[1], 10, 64)
83+
authTime := &AuthTime{
84+
SignStartTime: time.Unix(st, 0),
85+
SignEndTime: time.Unix(et, 0),
86+
KeyStartTime: time.Unix(st, 0),
87+
KeyEndTime: time.Unix(et, 0),
88+
}
89+
host := strings.TrimLeft(uri, "http://")
90+
req, _ := http.NewRequest("GET", uri, nil)
91+
req.Header.Add("Host", host)
92+
expect := newAuthorization(ak, sk, req, authTime)
93+
if expect != auth {
94+
t.Errorf("CVMCredentialsTransport Authorization error, want:%v, return:%v\n", expect, auth)
95+
}
96+
})
97+
98+
// CVM http server
99+
cvm_mux := http.NewServeMux()
100+
cvm_server := httptest.NewServer(cvm_mux)
101+
defer cvm_server.Close()
102+
// 将默认 CVM Host 修改成测试IP:PORT
103+
defaultCVMMetaHost = strings.TrimLeft(cvm_server.URL, "http://")
104+
105+
cvm_mux.HandleFunc("/"+defaultCVMCredURI, func(w http.ResponseWriter, r *http.Request) {
106+
fmt.Fprint(w, "cvm_read_cos_only")
107+
})
108+
cvm_mux.HandleFunc("/"+defaultCVMCredURI+"/cvm_read_cos_only", func(w http.ResponseWriter, r *http.Request) {
109+
fmt.Fprint(w, fmt.Sprintf(`{
110+
"TmpSecretId": "%s",
111+
"TmpSecretKey": "%s",
112+
"ExpiredTime": %v,
113+
"Expiration": "now",
114+
"Token": "%s",
115+
"Code": "Success"
116+
}`, ak, sk, time.Now().Unix()+3600, token))
117+
})
118+
119+
client.client.Transport = &CVMCredentialsTransport{}
120+
req, _ := http.NewRequest("GET", client.BaseURL.BucketURL.String(), nil)
121+
client.doAPI(context.Background(), req, nil, true)
122+
123+
req, _ = http.NewRequest("GET", client.BaseURL.BucketURL.String(), nil)
124+
client.doAPI(context.Background(), req, nil, true)
125+
}

cos.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"reflect"
1414
"strings"
1515
"text/template"
16+
"time"
1617

1718
"strconv"
1819

@@ -72,9 +73,15 @@ func NewBucketURL(bucketName, region string, secure bool) *url.URL {
7273
return u
7374
}
7475

76+
type RetryOptions struct {
77+
Count int
78+
Interval time.Duration
79+
StatusCode []int
80+
}
7581
type Config struct {
7682
EnableCRC bool
7783
RequestBodyClose bool
84+
RetryOpt RetryOptions
7885
}
7986

8087
// Client is a client manages communication with the COS API.
@@ -125,6 +132,10 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
125132
Conf: &Config{
126133
EnableCRC: true,
127134
RequestBodyClose: false,
135+
RetryOpt: RetryOptions{
136+
Count: 3,
137+
Interval: time.Duration(0),
138+
},
128139
},
129140
}
130141
c.common.client = c
@@ -309,14 +320,31 @@ func (c *Client) doRetry(ctx context.Context, opt *sendOptions) (resp *Response,
309320
return
310321
}
311322
}
323+
count := 1
324+
if count < c.Conf.RetryOpt.Count {
325+
count = c.Conf.RetryOpt.Count
326+
}
312327
nr := 0
313-
for nr < 3 {
328+
interval := c.Conf.RetryOpt.Interval
329+
for nr < count {
314330
resp, err = c.send(ctx, opt)
315331
if err != nil {
316332
if resp != nil && resp.StatusCode <= 499 {
317-
break
333+
dobreak := true
334+
for _, v := range c.Conf.RetryOpt.StatusCode {
335+
if resp.StatusCode == v {
336+
dobreak = false
337+
break
338+
}
339+
}
340+
if dobreak {
341+
break
342+
}
318343
}
319344
nr++
345+
if interval > 0 && nr < count {
346+
time.Sleep(interval)
347+
}
320348
continue
321349
}
322350
break

example/object/get_with_retry.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
"net/http"
9+
"os"
10+
"time"
11+
12+
"github.com/agin719/cos-go-sdk-v5"
13+
"github.com/tencentyun/cos-go-sdk-v5/debug"
14+
)
15+
16+
func log_status(err error) {
17+
if err == nil {
18+
return
19+
}
20+
if cos.IsNotFoundError(err) {
21+
// WARN
22+
fmt.Println("WARN: Resource is not existed")
23+
} else if e, ok := cos.IsCOSError(err); ok {
24+
fmt.Printf("ERROR: Code: %v\n", e.Code)
25+
fmt.Printf("ERROR: Message: %v\n", e.Message)
26+
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
27+
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
28+
// ERROR
29+
} else {
30+
fmt.Printf("ERROR: %v\n", err)
31+
// ERROR
32+
}
33+
}
34+
35+
func main() {
36+
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
37+
b := &cos.BaseURL{BucketURL: u}
38+
c := cos.NewClient(b, &http.Client{
39+
Transport: &cos.AuthorizationTransport{
40+
SecretID: os.Getenv("COS_SECRETID"),
41+
SecretKey: os.Getenv("COS_SECRETKEY"),
42+
Transport: &debug.DebugRequestTransport{
43+
RequestHeader: true,
44+
// Notice when put a large file and set need the request body, might happend out of memory error.
45+
RequestBody: false,
46+
ResponseHeader: true,
47+
ResponseBody: false,
48+
},
49+
},
50+
})
51+
// Get 请求配置重试
52+
c.Conf.RetryOpt.Count = 3 // 错误重试次数,默认重试3次
53+
c.Conf.RetryOpt.Interval = time.Millisecond // 错误重试间隔时间,默认0
54+
c.Conf.RetryOpt.StatusCode = []int{200} // 默认5xx都会重试,该参数配置其余需要重试的响应码
55+
56+
name := "exampleobject"
57+
_, err := c.Object.Get(context.Background(), name, nil)
58+
log_status(err)
59+
}

object_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func TestObjectService_GetRetry(t *testing.T) {
121121
ResponseHeaderTimeout: 1 * time.Second,
122122
},
123123
})
124+
client.Conf.RetryOpt.StatusCode = []int{499}
124125
name := "test/hello.txt"
125126
contentLength := 1024 * 1024 * 10
126127
data := make([]byte, contentLength)
@@ -132,6 +133,10 @@ func TestObjectService_GetRetry(t *testing.T) {
132133
}
133134
index++
134135
if index%3 != 0 {
136+
if index > 6 {
137+
w.WriteHeader(499)
138+
return
139+
}
135140
time.Sleep(time.Second * 2)
136141
}
137142
testFormValues(t, r, vs)
@@ -163,6 +168,9 @@ func TestObjectService_GetRetry(t *testing.T) {
163168
t.Errorf("Object.Get Failed")
164169
}
165170
}
171+
if index != 9 {
172+
t.Errorf("retry time error, retry count: %v\n", index)
173+
}
166174
}
167175

168176
func TestObjectService_GetPresignedURL(t *testing.T) {

0 commit comments

Comments
 (0)