Skip to content

Commit 0c9a029

Browse files
authored
Merge pull request #156 from agin719/cos-v4-dev
Cos v4 dev
2 parents 19a274d + d7b154b commit 0c9a029

File tree

8 files changed

+470
-6
lines changed

8 files changed

+470
-6
lines changed

auth.go

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package cos
33
import (
44
"crypto/hmac"
55
"crypto/sha1"
6+
"encoding/json"
67
"fmt"
78
"hash"
9+
"io/ioutil"
810
"net/http"
911
"net/url"
1012
"sort"
@@ -13,9 +15,18 @@ import (
1315
"time"
1416
)
1517

16-
const sha1SignAlgorithm = "sha1"
17-
const privateHeaderPrefix = "x-cos-"
18-
const defaultAuthExpire = time.Hour
18+
const (
19+
sha1SignAlgorithm = "sha1"
20+
privateHeaderPrefix = "x-cos-"
21+
defaultAuthExpire = time.Hour
22+
)
23+
24+
var (
25+
defaultCVMAuthExpire = int64(600)
26+
defaultCVMSchema = "http"
27+
defaultCVMMetaHost = "metadata.tencentyun.com"
28+
defaultCVMCredURI = "latest/meta-data/cam/security-credentials"
29+
)
1930

2031
// 需要校验的 Headers 列表
2132
var needSignHeaders = map[string]bool{
@@ -318,3 +329,120 @@ func (t *AuthorizationTransport) transport() http.RoundTripper {
318329
}
319330
return http.DefaultTransport
320331
}
332+
333+
type CVMSecurityCredentials struct {
334+
TmpSecretId string `json:",omitempty"`
335+
TmpSecretKey string `json:",omitempty"`
336+
ExpiredTime int64 `json:",omitempty"`
337+
Expiration string `json:",omitempty"`
338+
Token string `json:",omitempty"`
339+
Code string `json:",omitempty"`
340+
}
341+
342+
type CVMCredentialsTransport struct {
343+
RoleName string
344+
Transport http.RoundTripper
345+
secretID string
346+
secretKey string
347+
sessionToken string
348+
expiredTime int64
349+
rwLocker sync.RWMutex
350+
}
351+
352+
func (t *CVMCredentialsTransport) GetRoles() ([]string, error) {
353+
urlname := fmt.Sprintf("%s://%s/%s", defaultCVMSchema, defaultCVMMetaHost, defaultCVMCredURI)
354+
resp, err := http.Get(urlname)
355+
if err != nil {
356+
return nil, err
357+
}
358+
defer resp.Body.Close()
359+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
360+
bs, _ := ioutil.ReadAll(resp.Body)
361+
return nil, fmt.Errorf("get cvm security-credentials role failed, StatusCode: %v, Body: %v", resp.StatusCode, string(bs))
362+
}
363+
bs, err := ioutil.ReadAll(resp.Body)
364+
if err != nil {
365+
return nil, err
366+
}
367+
roles := strings.Split(strings.TrimSpace(string(bs)), "\n")
368+
if len(roles) == 0 {
369+
return nil, fmt.Errorf("get cvm security-credentials role failed, No valid cam role was found")
370+
}
371+
return roles, nil
372+
}
373+
374+
// https://cloud.tencent.com/document/product/213/4934
375+
func (t *CVMCredentialsTransport) UpdateCredential(now int64) (string, string, string, error) {
376+
t.rwLocker.Lock()
377+
defer t.rwLocker.Unlock()
378+
if t.expiredTime > now+defaultCVMAuthExpire {
379+
return t.secretID, t.secretKey, t.sessionToken, nil
380+
}
381+
roleName := t.RoleName
382+
if roleName == "" {
383+
roles, err := t.GetRoles()
384+
if err != nil {
385+
return t.secretID, t.secretKey, t.sessionToken, err
386+
}
387+
roleName = roles[0]
388+
}
389+
urlname := fmt.Sprintf("%s://%s/%s/%s", defaultCVMSchema, defaultCVMMetaHost, defaultCVMCredURI, roleName)
390+
resp, err := http.Get(urlname)
391+
if err != nil {
392+
return t.secretID, t.secretKey, t.sessionToken, err
393+
}
394+
defer resp.Body.Close()
395+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
396+
bs, _ := ioutil.ReadAll(resp.Body)
397+
return t.secretID, t.secretKey, t.sessionToken, fmt.Errorf("call cvm security-credentials failed, StatusCode: %v, Body: %v", resp.StatusCode, string(bs))
398+
}
399+
var cred CVMSecurityCredentials
400+
err = json.NewDecoder(resp.Body).Decode(&cred)
401+
if err != nil {
402+
return t.secretID, t.secretKey, t.sessionToken, err
403+
}
404+
if cred.Code != "Success" {
405+
return t.secretID, t.secretKey, t.sessionToken, fmt.Errorf("call cvm security-credentials failed, Code:%v", cred.Code)
406+
}
407+
t.secretID, t.secretKey, t.sessionToken, t.expiredTime = cred.TmpSecretId, cred.TmpSecretKey, cred.Token, cred.ExpiredTime
408+
return t.secretID, t.secretKey, t.sessionToken, nil
409+
}
410+
411+
func (t *CVMCredentialsTransport) GetCredential() (string, string, string, error) {
412+
now := time.Now().Unix()
413+
t.rwLocker.RLock()
414+
// 提前 defaultCVMAuthExpire 获取重新获取临时密钥
415+
if t.expiredTime <= now+defaultCVMAuthExpire {
416+
expiredTime := t.expiredTime
417+
t.rwLocker.RUnlock()
418+
secretID, secretKey, secretToken, err := t.UpdateCredential(now)
419+
// 获取临时密钥失败但密钥未过期
420+
if err != nil && now < expiredTime {
421+
err = nil
422+
}
423+
return secretID, secretKey, secretToken, err
424+
}
425+
defer t.rwLocker.RUnlock()
426+
return t.secretID, t.secretKey, t.sessionToken, nil
427+
}
428+
429+
func (t *CVMCredentialsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
430+
ak, sk, token, err := t.GetCredential()
431+
if err != nil {
432+
return nil, err
433+
}
434+
req = cloneRequest(req)
435+
// 增加 Authorization header
436+
authTime := NewAuthTime(defaultAuthExpire)
437+
AddAuthorizationHeader(ak, sk, token, req, authTime)
438+
439+
resp, err := t.transport().RoundTrip(req)
440+
return resp, err
441+
}
442+
443+
func (t *CVMCredentialsTransport) transport() http.RoundTripper {
444+
if t.Transport != nil {
445+
return t.Transport
446+
}
447+
return http.DefaultTransport
448+
}

cos.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type BaseURL struct {
4444
BatchURL *url.URL
4545
// 访问 CI 的基础 URL
4646
CIURL *url.URL
47+
// 访问 Fetch Task 的基础 URL
48+
FetchURL *url.URL
4749
}
4850

4951
// NewBucketURL 生成 BaseURL 所需的 BucketURL
@@ -110,6 +112,7 @@ func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
110112
baseURL.ServiceURL = uri.ServiceURL
111113
baseURL.BatchURL = uri.BatchURL
112114
baseURL.CIURL = uri.CIURL
115+
baseURL.FetchURL = uri.FetchURL
113116
}
114117
if baseURL.ServiceURL == nil {
115118
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)

cos_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ func setup() {
3030
// test server
3131
mux = http.NewServeMux()
3232
server = httptest.NewServer(mux)
33-
3433
u, _ := url.Parse(server.URL)
35-
client = NewClient(&BaseURL{u, u, u, u}, nil)
34+
client = NewClient(&BaseURL{u, u, u, u, u}, nil)
3635
}
3736

3837
// teardown closes the test HTTP server.

error.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package cos
22

33
import (
4+
"encoding/json"
45
"encoding/xml"
56
"fmt"
67
"io/ioutil"
78
"net/http"
9+
"strconv"
10+
"strings"
811
)
912

1013
// ErrorResponse 包含 API 返回的错误信息
@@ -39,6 +42,12 @@ func (r *ErrorResponse) Error() string {
3942
r.Response.StatusCode, r.Code, r.Message, RequestID, TraceID)
4043
}
4144

45+
type jsonError struct {
46+
Code int `json:"code,omitempty"`
47+
Message string `json:"message,omitempty"`
48+
RequestID string `json:"request_id,omitempty"`
49+
}
50+
4251
// 检查 response 是否是出错时的返回的 response
4352
func checkResponse(r *http.Response) error {
4453
if c := r.StatusCode; 200 <= c && c <= 299 {
@@ -49,6 +58,18 @@ func checkResponse(r *http.Response) error {
4958
if err == nil && data != nil {
5059
xml.Unmarshal(data, errorResponse)
5160
}
61+
// 是否为 json 格式
62+
if errorResponse.Code == "" {
63+
ctype := strings.TrimLeft(r.Header.Get("Content-Type"), " ")
64+
if strings.HasPrefix(ctype, "application/json") {
65+
var jerror jsonError
66+
json.Unmarshal(data, &jerror)
67+
errorResponse.Code = strconv.Itoa(jerror.Code)
68+
errorResponse.Message = jerror.Message
69+
errorResponse.RequestID = jerror.RequestID
70+
}
71+
72+
}
5273
return errorResponse
5374
}
5475

example/object/fetch_task.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/tencentyun/cos-go-sdk-v5"
7+
"github.com/tencentyun/cos-go-sdk-v5/debug"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
"time"
12+
)
13+
14+
func log_status(err error) {
15+
if err == nil {
16+
return
17+
}
18+
if cos.IsNotFoundError(err) {
19+
// WARN
20+
fmt.Println("WARN: Resource is not existed")
21+
} else if e, ok := cos.IsCOSError(err); ok {
22+
fmt.Printf("ERROR: Code: %v\n", e.Code)
23+
fmt.Printf("ERROR: Message: %v\n", e.Message)
24+
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
25+
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
26+
// ERROR
27+
} else {
28+
fmt.Printf("ERROR: %v\n", err)
29+
// ERROR
30+
}
31+
}
32+
33+
func main() {
34+
bucket := "test-1259654469"
35+
bu, _ := url.Parse("https://" + bucket + ".cos.ap-guangzhou.myqcloud.com")
36+
u, _ := url.Parse("http://ap-guangzhou.migration.myqcloud.com")
37+
b := &cos.BaseURL{BucketURL: bu, FetchURL: 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+
RequestBody: true,
45+
ResponseHeader: true,
46+
ResponseBody: true,
47+
},
48+
},
49+
})
50+
opt := &cos.PutFetchTaskOptions{
51+
Url: "http://" + bucket + ".cos.ap-guangzhou.myqcloud.com/exampleobject",
52+
Key: "exampleobject",
53+
}
54+
55+
res, _, err := c.Object.PutFetchTask(context.Background(), bucket, opt)
56+
log_status(err)
57+
fmt.Printf("res: %+v\n", res)
58+
59+
time.Sleep(time.Second * 3)
60+
61+
rs, _, err := c.Object.GetFetchTask(context.Background(), bucket, res.Data.TaskId)
62+
log_status(err)
63+
fmt.Printf("res: %+v\n", rs)
64+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
"net/http"
9+
10+
"github.com/tencentyun/cos-go-sdk-v5"
11+
"github.com/tencentyun/cos-go-sdk-v5/debug"
12+
)
13+
14+
func log_status(err error) {
15+
if err == nil {
16+
return
17+
}
18+
if cos.IsNotFoundError(err) {
19+
// WARN
20+
fmt.Println("WARN: Resource is not existed")
21+
} else if e, ok := cos.IsCOSError(err); ok {
22+
fmt.Printf("ERROR: Code: %v\n", e.Code)
23+
fmt.Printf("ERROR: Message: %v\n", e.Message)
24+
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
25+
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
26+
// ERROR
27+
} else {
28+
fmt.Printf("ERROR: %v\n", err)
29+
// ERROR
30+
}
31+
}
32+
33+
func main() {
34+
u, _ := url.Parse("https://test-1259654469.cos.ap-guangzhou.myqcloud.com")
35+
b := &cos.BaseURL{BucketURL: u}
36+
c := cos.NewClient(b, &http.Client{
37+
// 使用 CVMCredentialsTransport
38+
Transport: &cos.CVMCredentialsTransport{
39+
Transport: &debug.DebugRequestTransport{
40+
RequestHeader: true,
41+
// Notice when put a large file and set need the request body, might happend out of memory error.
42+
RequestBody: false,
43+
ResponseHeader: true,
44+
ResponseBody: false,
45+
},
46+
},
47+
})
48+
49+
name := "exampleobject"
50+
_, err := c.Object.Get(context.Background(), name, nil)
51+
log_status(err)
52+
}

0 commit comments

Comments
 (0)