alantong
6 years ago
commit
6ad265fc0c
78 changed files with 6171 additions and 0 deletions
-
7.bumpversion.cfg
-
29.gitignore
-
27.travis.yml
-
21LICENSE
-
20Makefile
-
96README.md
-
264auth.go
-
51auth_test.go
-
106bucket.go
-
62bucket_acl.go
-
172bucket_acl_test.go
-
77bucket_cors.go
-
138bucket_cors_test.go
-
107bucket_lifecycle.go
-
140bucket_lifecycle_test.go
-
30bucket_location.go
-
39bucket_location_test.go
-
59bucket_part.go
-
121bucket_part_test.go
-
75bucket_tagging.go
-
115bucket_tagging_test.go
-
152bucket_test.go
-
352cos.go
-
156cos_test.go
-
70debug/http.go
-
78debug/http_test.go
-
29doc.go
-
49error.go
-
56error_test.go
-
8example/README.md
-
36example/bucket/delete.go
-
36example/bucket/deleteCORS.go
-
35example/bucket/deleteLifecycle.go
-
36example/bucket/deleteTagging.go
-
46example/bucket/get.go
-
40example/bucket/getACL.go
-
41example/bucket/getCORS.go
-
40example/bucket/getLifecycle.go
-
38example/bucket/getLocation.go
-
40example/bucket/getTagging.go
-
38example/bucket/head.go
-
43example/bucket/listMultipartUploads.go
-
39example/bucket/put.go
-
65example/bucket/putACL.go
-
53example/bucket/putCORS.go
-
58example/bucket/putLifecycle.go
-
54example/bucket/putTagging.go
-
43example/object/MutiUpload.go
-
43example/object/abortMultipartUpload.go
-
75example/object/append.go
-
95example/object/completeMultipartUpload.go
-
63example/object/copy.go
-
36example/object/delete.go
-
102example/object/deleteMultiple.go
-
54example/object/get.go
-
40example/object/getACL.go
-
45example/object/getAnonymous.go
-
35example/object/head.go
-
38example/object/initiateMultipartUpload.go
-
81example/object/listParts.go
-
39example/object/options.go
-
54example/object/put.go
-
64example/object/putACL.go
-
53example/object/uploadFile.go
-
53example/object/uploadPart.go
-
36example/service/get.go
-
101example/sts/sts.go
-
57example/test.sh
-
85helper.go
-
24helper_test.go
-
346object.go
-
63object_acl.go
-
174object_acl_test.go
-
178object_part.go
-
273object_part_test.go
-
274object_test.go
-
37service.go
-
66service_test.go
@ -0,0 +1,7 @@ |
|||
[bumpversion] |
|||
commit = True |
|||
tag = True |
|||
current_version = 0.7.0 |
|||
|
|||
[bumpversion:file:cos.go] |
|||
|
@ -0,0 +1,29 @@ |
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects) |
|||
*.o |
|||
*.a |
|||
*.so |
|||
|
|||
# Folders |
|||
_obj |
|||
_test |
|||
|
|||
# Architecture specific extensions/prefixes |
|||
*.[568vq] |
|||
[568vq].out |
|||
|
|||
*.cgo1.go |
|||
*.cgo2.c |
|||
_cgo_defun.c |
|||
_cgo_gotypes.go |
|||
_cgo_export.* |
|||
|
|||
_testmain.go |
|||
|
|||
*.exe |
|||
*.test |
|||
*.prof |
|||
dist/ |
|||
cover.html |
|||
cover.out |
|||
covprofile |
|||
coverage.html |
@ -0,0 +1,27 @@ |
|||
language: go |
|||
go: |
|||
- 1.6 |
|||
- 1.7 |
|||
- 1.8 |
|||
- 1.9 |
|||
- master |
|||
|
|||
sudo: false |
|||
|
|||
before_install: |
|||
- go get github.com/mattn/goveralls |
|||
|
|||
install: |
|||
- go get |
|||
- go build |
|||
|
|||
script: |
|||
- make test |
|||
- go test -coverprofile=cover.out github.com/tencentyun/cos-go-sdk-v5 |
|||
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out |
|||
|
|||
matrix: |
|||
allow_failures: |
|||
- go: 1.6 |
|||
- go: 1.7 |
|||
- go: master |
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2017 mozillazg |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,20 @@ |
|||
help: |
|||
@echo "test run test" |
|||
@echo "lint run lint" |
|||
@echo "example run examples" |
|||
|
|||
.PHONY: test |
|||
test: |
|||
go test -v -cover -coverprofile cover.out |
|||
go tool cover -html=cover.out -o cover.html |
|||
|
|||
.PHONY: lint |
|||
lint: |
|||
gofmt -s -w . |
|||
goimports -w . |
|||
golint . |
|||
go vet |
|||
|
|||
.PHONY: example |
|||
example: |
|||
cd examples && sh test.sh |
@ -0,0 +1,96 @@ |
|||
# cos-go-sdk-v5 |
|||
|
|||
腾讯云对象存储服务 COS(Cloud Object Storage) Go SDK(API 版本:V5 版本的 XML API)。 |
|||
|
|||
## Install |
|||
|
|||
`go get -u github.com/tencentyun/cos-go-sdk-v5` |
|||
|
|||
|
|||
## Usage |
|||
|
|||
```go |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"net/url" |
|||
"os" |
|||
"time" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
) |
|||
|
|||
func main() { |
|||
//将<bucket>和<region>修改为真实的信息 |
|||
//bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式 |
|||
u, _ := url.Parse("https://<bucket>.cos.<region>.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
//设置超时时间 |
|||
Timeout: 100 * time.Second, |
|||
Transport: &cos.AuthorizationTransport{ |
|||
//如实填写账号和密钥,也可以设置为环境变量 |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
}, |
|||
}) |
|||
|
|||
name := "test/hello.txt" |
|||
resp, err := c.Object.Get(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
bs, _ := ioutil.ReadAll(resp.Body) |
|||
resp.Body.Close() |
|||
fmt.Printf("%s\n", string(bs)) |
|||
} |
|||
``` |
|||
|
|||
所有的 API 在 [example](./example/) 目录下都有对应的使用示例。 |
|||
|
|||
Service API: |
|||
|
|||
* [x] Get Service(使用示例:[service/get.go](./example/service/get.go)) |
|||
|
|||
Bucket API: |
|||
|
|||
* [x] Get Bucket(使用示例:[bucket/get.go](./example/bucket/get.go)) |
|||
* [x] Get Bucket ACL(使用示例:[bucket/getACL.go](./example/bucket/getACL.go)) |
|||
* [x] Get Bucket CORS(使用示例:[bucket/getCORS.go](./example/bucket/getCORS.go)) |
|||
* [x] Get Bucket Location(使用示例:[bucket/getLocation.go](./example/bucket/getLocation.go)) |
|||
* [x] Get Buket Lifecycle(使用示例:[bucket/getLifecycle.go](./example/bucket/getLifecycle.go)) |
|||
* [x] Get Bucket Tagging(使用示例:[bucket/getTagging.go](./example/bucket/getTagging.go)) |
|||
* [x] Put Bucket(使用示例:[bucket/put.go](./example/bucket/put.go)) |
|||
* [x] Put Bucket ACL(使用示例:[bucket/putACL.go](./example/bucket/putACL.go)) |
|||
* [x] Put Bucket CORS(使用示例:[bucket/putCORS.go](./example/bucket/putCORS.go)) |
|||
* [x] Put Bucket Lifecycle(使用示例:[bucket/putLifecycle.go](./example/bucket/putLifecycle.go)) |
|||
* [x] Put Bucket Tagging(使用示例:[bucket/putTagging.go](./example/bucket/putTagging.go)) |
|||
* [x] Delete Bucket(使用示例:[bucket/delete.go](./example/bucket/delete.go)) |
|||
* [x] Delete Bucket CORS(使用示例:[bucket/deleteCORS.go](./example/bucket/deleteCORS.go)) |
|||
* [x] Delete Bucket Lifecycle(使用示例:[bucket/deleteLifecycle.go](./example/bucket/deleteLifecycle.go)) |
|||
* [x] Delete Bucket Tagging(使用示例:[bucket/deleteTagging.go](./example/bucket/deleteTagging.go)) |
|||
* [x] Head Bucket(使用示例:[bucket/head.go](./example/bucket/head.go)) |
|||
* [x] List Multipart Uploads(使用示例:[bucket/listMultipartUploads.go](./example/bucket/listMultipartUploads.go)) |
|||
|
|||
Object API: |
|||
|
|||
* [x] Append Object(使用示例:[object/append.go](./example/object/append.go)) |
|||
* [x] Get Object(使用示例:[object/get.go](./example/object/get.go)) |
|||
* [x] Get Object ACL(使用示例:[object/getACL.go](./example/object/getACL.go)) |
|||
* [x] Put Object(使用示例:[object/put.go](./example/object/put.go)) |
|||
* [x] Put Object ACL(使用示例:[object/putACL.go](./example/object/putACL.go)) |
|||
* [x] Put Object Copy(使用示例:[object/copy.go](./example/object/copy.go)) |
|||
* [x] Delete Object(使用示例:[object/delete.go](./example/object/delete.go)) |
|||
* [x] Delete Multiple Object(使用示例:[object/deleteMultiple.go](./example/object/deleteMultiple.go)) |
|||
* [x] Head Object(使用示例:[object/head.go](./example/object/head.go)) |
|||
* [x] Options Object(使用示例:[object/options.go](./example/object/options.go)) |
|||
* [x] Initiate Multipart Upload(使用示例:[object/initiateMultipartUpload.go](./example/object/initiateMultipartUpload.go)) |
|||
* [x] Upload Part(使用示例:[object/uploadPart.go](./example/object/uploadPart.go)) |
|||
* [x] List Parts(使用示例:[object/listParts.go](./example/object/listParts.go)) |
|||
* [x] Complete Multipart Upload(使用示例:[object/completeMultipartUpload.go](./example/object/completeMultipartUpload.go)) |
|||
* [x] Abort Multipart Upload(使用示例:[object/abortMultipartUpload.go](./example/object/abortMultipartUpload.go)) |
|||
* [x] Mutipart Upload(使用示例:[object/MutiUpload.go](./example/object/MutiUpload.go)) |
@ -0,0 +1,264 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"crypto/hmac" |
|||
"crypto/sha1" |
|||
"fmt" |
|||
"hash" |
|||
"net/http" |
|||
"net/url" |
|||
"sort" |
|||
"strings" |
|||
"sync" |
|||
"time" |
|||
) |
|||
|
|||
const sha1SignAlgorithm = "sha1" |
|||
const privateHeaderPrefix = "x-cos-" |
|||
const defaultAuthExpire = time.Hour |
|||
|
|||
// 需要校验的 Headers 列表
|
|||
var needSignHeaders = map[string]bool{ |
|||
"host": true, |
|||
"range": true, |
|||
"x-cos-acl": true, |
|||
"x-cos-grant-read": true, |
|||
"x-cos-grant-write": true, |
|||
"x-cos-grant-full-control": true, |
|||
"response-content-type": true, |
|||
"response-content-language": true, |
|||
"response-expires": true, |
|||
"response-cache-control": true, |
|||
"response-content-disposition": true, |
|||
"response-content-encoding": true, |
|||
"cache-control": true, |
|||
"content-disposition": true, |
|||
"content-encoding": true, |
|||
"content-type": true, |
|||
"content-length": true, |
|||
"content-md5": true, |
|||
"expect": true, |
|||
"expires": true, |
|||
"x-cos-content-sha1": true, |
|||
"x-cos-storage-class": true, |
|||
"if-modified-since": true, |
|||
"origin": true, |
|||
"access-control-request-method": true, |
|||
"access-control-request-headers": true, |
|||
"x-cos-object-type": true, |
|||
} |
|||
|
|||
// AuthTime 用于生成签名所需的 q-sign-time 和 q-key-time 相关参数
|
|||
type AuthTime struct { |
|||
SignStartTime time.Time |
|||
SignEndTime time.Time |
|||
KeyStartTime time.Time |
|||
KeyEndTime time.Time |
|||
} |
|||
|
|||
// NewAuthTime 生成 AuthTime 的便捷函数
|
|||
//
|
|||
// expire: 从现在开始多久过期.
|
|||
func NewAuthTime(expire time.Duration) *AuthTime { |
|||
signStartTime := time.Now() |
|||
keyStartTime := signStartTime |
|||
signEndTime := signStartTime.Add(expire) |
|||
keyEndTime := signEndTime |
|||
return &AuthTime{ |
|||
SignStartTime: signStartTime, |
|||
SignEndTime: signEndTime, |
|||
KeyStartTime: keyStartTime, |
|||
KeyEndTime: keyEndTime, |
|||
} |
|||
} |
|||
|
|||
// signString return q-sign-time string
|
|||
func (a *AuthTime) signString() string { |
|||
return fmt.Sprintf("%d;%d", a.SignStartTime.Unix(), a.SignEndTime.Unix()) |
|||
} |
|||
|
|||
// keyString return q-key-time string
|
|||
func (a *AuthTime) keyString() string { |
|||
return fmt.Sprintf("%d;%d", a.KeyStartTime.Unix(), a.KeyEndTime.Unix()) |
|||
} |
|||
|
|||
// newAuthorization 通过一系列步骤生成最终需要的 Authorization 字符串
|
|||
func newAuthorization(secretID, secretKey string, req *http.Request, authTime *AuthTime) string { |
|||
signTime := authTime.signString() |
|||
keyTime := authTime.keyString() |
|||
signKey := calSignKey(secretKey, keyTime) |
|||
|
|||
formatHeaders := *new(string) |
|||
signedHeaderList := *new([]string) |
|||
formatHeaders, signedHeaderList = genFormatHeaders(req.Header) |
|||
formatParameters, signedParameterList := genFormatParameters(req.URL.Query()) |
|||
formatString := genFormatString(req.Method, *req.URL, formatParameters, formatHeaders) |
|||
|
|||
stringToSign := calStringToSign(sha1SignAlgorithm, keyTime, formatString) |
|||
signature := calSignature(signKey, stringToSign) |
|||
|
|||
return genAuthorization( |
|||
secretID, signTime, keyTime, signature, signedHeaderList, |
|||
signedParameterList, |
|||
) |
|||
} |
|||
|
|||
// AddAuthorizationHeader 给 req 增加签名信息
|
|||
func AddAuthorizationHeader(secretID, secretKey string, sessionToken string, req *http.Request, authTime *AuthTime) { |
|||
auth := newAuthorization(secretID, secretKey, req, |
|||
authTime, |
|||
) |
|||
if len(sessionToken) > 0 { |
|||
req.Header.Set("x-cos-security-token", sessionToken) |
|||
} |
|||
req.Header.Set("Authorization", auth) |
|||
} |
|||
|
|||
// calSignKey 计算 SignKey
|
|||
func calSignKey(secretKey, keyTime string) string { |
|||
digest := calHMACDigest(secretKey, keyTime, sha1SignAlgorithm) |
|||
return fmt.Sprintf("%x", digest) |
|||
} |
|||
|
|||
// calStringToSign 计算 StringToSign
|
|||
func calStringToSign(signAlgorithm, signTime, formatString string) string { |
|||
h := sha1.New() |
|||
h.Write([]byte(formatString)) |
|||
return fmt.Sprintf("%s\n%s\n%x\n", signAlgorithm, signTime, h.Sum(nil)) |
|||
} |
|||
|
|||
// calSignature 计算 Signature
|
|||
func calSignature(signKey, stringToSign string) string { |
|||
digest := calHMACDigest(signKey, stringToSign, sha1SignAlgorithm) |
|||
return fmt.Sprintf("%x", digest) |
|||
} |
|||
|
|||
// genAuthorization 生成 Authorization
|
|||
func genAuthorization(secretID, signTime, keyTime, signature string, signedHeaderList, signedParameterList []string) string { |
|||
return strings.Join([]string{ |
|||
"q-sign-algorithm=" + sha1SignAlgorithm, |
|||
"q-ak=" + secretID, |
|||
"q-sign-time=" + signTime, |
|||
"q-key-time=" + keyTime, |
|||
"q-header-list=" + strings.Join(signedHeaderList, ";"), |
|||
"q-url-param-list=" + strings.Join(signedParameterList, ";"), |
|||
"q-signature=" + signature, |
|||
}, "&") |
|||
} |
|||
|
|||
// genFormatString 生成 FormatString
|
|||
func genFormatString(method string, uri url.URL, formatParameters, formatHeaders string) string { |
|||
formatMethod := strings.ToLower(method) |
|||
formatURI := uri.Path |
|||
|
|||
return fmt.Sprintf("%s\n%s\n%s\n%s\n", formatMethod, formatURI, |
|||
formatParameters, formatHeaders, |
|||
) |
|||
} |
|||
|
|||
// genFormatParameters 生成 FormatParameters 和 SignedParameterList
|
|||
func genFormatParameters(parameters url.Values) (formatParameters string, signedParameterList []string) { |
|||
ps := url.Values{} |
|||
for key, values := range parameters { |
|||
for _, value := range values { |
|||
key = strings.ToLower(key) |
|||
ps.Add(key, value) |
|||
signedParameterList = append(signedParameterList, key) |
|||
} |
|||
} |
|||
//formatParameters = strings.ToLower(ps.Encode())
|
|||
formatParameters = ps.Encode() |
|||
sort.Strings(signedParameterList) |
|||
return |
|||
} |
|||
|
|||
// genFormatHeaders 生成 FormatHeaders 和 SignedHeaderList
|
|||
func genFormatHeaders(headers http.Header) (formatHeaders string, signedHeaderList []string) { |
|||
hs := url.Values{} |
|||
for key, values := range headers { |
|||
for _, value := range values { |
|||
key = strings.ToLower(key) |
|||
if isSignHeader(key) { |
|||
hs.Add(key, value) |
|||
signedHeaderList = append(signedHeaderList, key) |
|||
} |
|||
} |
|||
} |
|||
formatHeaders = hs.Encode() |
|||
sort.Strings(signedHeaderList) |
|||
return |
|||
} |
|||
|
|||
// HMAC 签名
|
|||
func calHMACDigest(key, msg, signMethod string) []byte { |
|||
var hashFunc func() hash.Hash |
|||
switch signMethod { |
|||
case "sha1": |
|||
hashFunc = sha1.New |
|||
default: |
|||
hashFunc = sha1.New |
|||
} |
|||
h := hmac.New(hashFunc, []byte(key)) |
|||
h.Write([]byte(msg)) |
|||
return h.Sum(nil) |
|||
} |
|||
|
|||
func isSignHeader(key string) bool { |
|||
for k, v := range needSignHeaders { |
|||
if key == k && v { |
|||
return true |
|||
} |
|||
} |
|||
return strings.HasPrefix(key, privateHeaderPrefix) |
|||
} |
|||
|
|||
// AuthorizationTransport 给请求增加 Authorization header
|
|||
type AuthorizationTransport struct { |
|||
SecretID string |
|||
SecretKey string |
|||
SessionToken string |
|||
rwLocker sync.RWMutex |
|||
// 签名多久过期
|
|||
Expire time.Duration |
|||
|
|||
Transport http.RoundTripper |
|||
} |
|||
|
|||
// SetCredential update the SecretID(ak), SercretKey(sk), sessiontoken
|
|||
func (t *AuthorizationTransport) SetCredential(ak, sk, token string) { |
|||
t.rwLocker.Lock() |
|||
defer t.rwLocker.Unlock() |
|||
t.SecretID = ak |
|||
t.SecretKey = sk |
|||
t.SessionToken = token |
|||
} |
|||
|
|||
// GetCredential get the ak, sk, token
|
|||
func (t *AuthorizationTransport) GetCredential() (string, string, string) { |
|||
t.rwLocker.RLock() |
|||
defer t.rwLocker.RUnlock() |
|||
return t.SecretID, t.SecretKey, t.SessionToken |
|||
} |
|||
|
|||
// RoundTrip implements the RoundTripper interface.
|
|||
func (t *AuthorizationTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
|||
req = cloneRequest(req) // per RoundTrip contract
|
|||
if t.Expire == time.Duration(0) { |
|||
t.Expire = defaultAuthExpire |
|||
} |
|||
|
|||
ak, sk, token := t.GetCredential() |
|||
// 增加 Authorization header
|
|||
authTime := NewAuthTime(t.Expire) |
|||
AddAuthorizationHeader(ak, sk, token, req, authTime) |
|||
|
|||
resp, err := t.transport().RoundTrip(req) |
|||
return resp, err |
|||
} |
|||
|
|||
func (t *AuthorizationTransport) transport() http.RoundTripper { |
|||
if t.Transport != nil { |
|||
return t.Transport |
|||
} |
|||
return http.DefaultTransport |
|||
} |
@ -0,0 +1,51 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"net/http" |
|||
"testing" |
|||
"time" |
|||
) |
|||
|
|||
func TestNewAuthorization(t *testing.T) { |
|||
expectAuthorization := `q-sign-algorithm=sha1&q-ak=QmFzZTY0IGlzIGEgZ2VuZXJp&q-sign-time=1480932292;1481012292&q-key-time=1480932292;1481012292&q-header-list=host;x-cos-content-sha1;x-cos-stroage-class&q-url-param-list=&q-signature=ce4ac0ecbcdb30538b3fee0a97cc6389694ce53a` |
|||
secretID := "QmFzZTY0IGlzIGEgZ2VuZXJp" |
|||
secretKey := "AKIDZfbOA78asKUYBcXFrJD0a1ICvR98JM" |
|||
host := "testbucket-125000000.cos.ap-guangzhou.myqcloud.com" |
|||
uri := "http://testbucket-125000000.cos.ap-guangzhou.myqcloud.com/testfile2" |
|||
startTime := time.Unix(int64(1480932292), 0) |
|||
endTime := time.Unix(int64(1481012292), 0) |
|||
|
|||
req, _ := http.NewRequest("PUT", uri, nil) |
|||
req.Header.Add("Host", host) |
|||
req.Header.Add("x-cos-content-sha1", "db8ac1c259eb89d4a131b253bacfca5f319d54f2") |
|||
req.Header.Add("x-cos-stroage-class", "nearline") |
|||
|
|||
authTime := &AuthTime{ |
|||
SignStartTime: startTime, |
|||
SignEndTime: endTime, |
|||
KeyStartTime: startTime, |
|||
KeyEndTime: endTime, |
|||
} |
|||
auth := newAuthorization(secretID, secretKey, req, authTime) |
|||
|
|||
if auth != expectAuthorization { |
|||
t.Errorf("NewAuthorization returned \n%#v, want \n%#v", auth, expectAuthorization) |
|||
} |
|||
} |
|||
|
|||
func TestAuthorizationTransport(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
auth := r.Header.Get("Authorization") |
|||
if auth == "" { |
|||
t.Error("AuthorizationTransport didn't add Authorization header") |
|||
} |
|||
}) |
|||
|
|||
client.client.Transport = &AuthorizationTransport{} |
|||
req, _ := http.NewRequest("GET", client.BaseURL.BucketURL.String(), nil) |
|||
client.doAPI(context.Background(), req, nil, true) |
|||
} |
@ -0,0 +1,106 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// BucketService ...
|
|||
//
|
|||
// Bucket 相关 API
|
|||
type BucketService service |
|||
|
|||
// BucketGetResult ...
|
|||
type BucketGetResult struct { |
|||
XMLName xml.Name `xml:"ListBucketResult"` |
|||
Name string |
|||
Prefix string `xml:"Prefix,omitempty"` |
|||
Marker string `xml:"Marker,omitempty"` |
|||
NextMarker string `xml:"NextMarker,omitempty"` |
|||
Delimiter string `xml:"Delimiter,omitempty"` |
|||
MaxKeys int |
|||
IsTruncated bool |
|||
Contents []Object `xml:"Contents,omitempty"` |
|||
CommonPrefixes []string `xml:"CommonPrefixes>Prefix,omitempty"` |
|||
EncodingType string `xml:"Encoding-Type,omitempty"` |
|||
} |
|||
|
|||
// BucketGetOptions ...
|
|||
type BucketGetOptions struct { |
|||
Prefix string `url:"prefix,omitempty"` |
|||
Delimiter string `url:"delimiter,omitempty"` |
|||
EncodingType string `url:"encoding-type,omitempty"` |
|||
Marker string `url:"marker,omitempty"` |
|||
MaxKeys int `url:"max-keys,omitempty"` |
|||
} |
|||
|
|||
// Get Bucket请求等同于 List Object请求,可以列出该Bucket下部分或者所有Object,发起该请求需要拥有Read权限。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7734
|
|||
func (s *BucketService) Get(ctx context.Context, opt *BucketGetOptions) (*BucketGetResult, *Response, error) { |
|||
var res BucketGetResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/", |
|||
method: http.MethodGet, |
|||
optQuery: opt, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// BucketPutOptions ...
|
|||
type BucketPutOptions ACLHeaderOptions |
|||
|
|||
// Put Bucket请求可以在指定账号下创建一个Bucket。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7738
|
|||
func (s *BucketService) Put(ctx context.Context, opt *BucketPutOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/", |
|||
method: http.MethodPut, |
|||
optHeader: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// Delete Bucket请求可以在指定账号下删除Bucket,删除之前要求Bucket为空。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7732
|
|||
func (s *BucketService) Delete(ctx context.Context) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/", |
|||
method: http.MethodDelete, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// Head Bucket请求可以确认是否存在该Bucket,是否有权限访问,Head的权限与Read一致。
|
|||
//
|
|||
// 当其存在时,返回 HTTP 状态码200;
|
|||
// 当无权限时,返回 HTTP 状态码403;
|
|||
// 当不存在时,返回 HTTP 状态码404。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7735
|
|||
func (s *BucketService) Head(ctx context.Context) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/", |
|||
method: http.MethodHead, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// Bucket ...
|
|||
type Bucket struct { |
|||
Name string |
|||
Region string `xml:"Location,omitempty"` |
|||
CreationDate string `xml:",omitempty"` |
|||
} |
@ -0,0 +1,62 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"net/http" |
|||
) |
|||
|
|||
// BucketGetACLResult ...
|
|||
type BucketGetACLResult ACLXml |
|||
|
|||
// GetACL 使用API读取Bucket的ACL表,只有所有者有权操作。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7733
|
|||
func (s *BucketService) GetACL(ctx context.Context) (*BucketGetACLResult, *Response, error) { |
|||
var res BucketGetACLResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?acl", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// BucketPutACLOptions ...
|
|||
type BucketPutACLOptions struct { |
|||
Header *ACLHeaderOptions `url:"-" xml:"-"` |
|||
Body *ACLXml `url:"-" header:"-"` |
|||
} |
|||
|
|||
// PutACL 使用API写入Bucket的ACL表,您可以通过Header:"x-cos-acl","x-cos-grant-read",
|
|||
// "x-cos-grant-write","x-cos-grant-full-control"传入ACL信息,也可以通过body以XML格式传入ACL信息,
|
|||
//
|
|||
// 但是只能选择Header和Body其中一种,否则返回冲突。
|
|||
//
|
|||
// Put Bucket ACL是一个覆盖操作,传入新的ACL将覆盖原有ACL。只有所有者有权操作。
|
|||
//
|
|||
// "x-cos-acl":枚举值为public-read,private;public-read意味这个Bucket有公有读私有写的权限,
|
|||
// private意味这个Bucket有私有读写的权限。
|
|||
//
|
|||
// "x-cos-grant-read":意味被赋予权限的用户拥有该Bucket的读权限
|
|||
// "x-cos-grant-write":意味被赋予权限的用户拥有该Bucket的写权限
|
|||
// "x-cos-grant-full-control":意味被赋予权限的用户拥有该Bucket的读写权限
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7737
|
|||
func (s *BucketService) PutACL(ctx context.Context, opt *BucketPutACLOptions) (*Response, error) { |
|||
header := opt.Header |
|||
body := opt.Body |
|||
if body != nil { |
|||
header = nil |
|||
} |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?acl", |
|||
method: http.MethodPut, |
|||
body: body, |
|||
optHeader: header, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
@ -0,0 +1,172 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_GetACL(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
vs := values{ |
|||
"acl": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<AccessControlPolicy> |
|||
<Owner> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>qcs::cam::uin/100000760461:uin/100000760461</DisplayName> |
|||
</Owner> |
|||
<AccessControlList> |
|||
<Grant> |
|||
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="RootAccount"> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>qcs::cam::uin/100000760461:uin/100000760461</DisplayName> |
|||
</Grantee> |
|||
<Permission>FULL_CONTROL</Permission> |
|||
</Grant> |
|||
<Grant> |
|||
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="RootAccount"> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>qcs::cam::uin/100000760461:uin/100000760461</DisplayName> |
|||
</Grantee> |
|||
<Permission>READ</Permission> |
|||
</Grant> |
|||
</AccessControlList> |
|||
</AccessControlPolicy>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Bucket.GetACL(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.GetACL returned error: %v", err) |
|||
} |
|||
|
|||
want := &BucketGetACLResult{ |
|||
XMLName: xml.Name{Local: "AccessControlPolicy"}, |
|||
Owner: &Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
AccessControlList: []ACLGrant{ |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
Permission: "FULL_CONTROL", |
|||
}, |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
Permission: "READ", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.GetACL returned %+v, want %+v", ref, want) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestBucketService_PutACL_with_header_opt(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketPutACLOptions{ |
|||
Header: &ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"acl": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
testHeader(t, r, "x-cos-acl", "private") |
|||
|
|||
want := 0 |
|||
v, _ := r.Body.Read([]byte{}) |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Bucket.PutACL request body: %#v, want %#v", v, want) |
|||
} |
|||
}) |
|||
|
|||
_, err := client.Bucket.PutACL(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.PutACL returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestBucketService_PutACL_with_body_opt(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketPutACLOptions{ |
|||
Body: &ACLXml{ |
|||
Owner: &Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
AccessControlList: []ACLGrant{ |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
|
|||
Permission: "FULL_CONTROL", |
|||
}, |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
Permission: "READ", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(ACLXml) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"acl": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
testHeader(t, r, "x-cos-acl", "") |
|||
|
|||
want := opt.Body |
|||
want.XMLName = xml.Name{Local: "AccessControlPolicy"} |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Bucket.PutACL request body: %+v, want %+v", v, want) |
|||
} |
|||
|
|||
}) |
|||
|
|||
_, err := client.Bucket.PutACL(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.PutACL returned error: %v", err) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,77 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// BucketCORSRule ...
|
|||
type BucketCORSRule struct { |
|||
ID string `xml:"ID,omitempty"` |
|||
AllowedMethods []string `xml:"AllowedMethod"` |
|||
AllowedOrigins []string `xml:"AllowedOrigin"` |
|||
AllowedHeaders []string `xml:"AllowedHeader,omitempty"` |
|||
MaxAgeSeconds int `xml:"MaxAgeSeconds,omitempty"` |
|||
ExposeHeaders []string `xml:"ExposeHeader,omitempty"` |
|||
} |
|||
|
|||
// BucketGetCORSResult ...
|
|||
type BucketGetCORSResult struct { |
|||
XMLName xml.Name `xml:"CORSConfiguration"` |
|||
Rules []BucketCORSRule `xml:"CORSRule,omitempty"` |
|||
} |
|||
|
|||
// GetCORS ...
|
|||
//
|
|||
// Get Bucket CORS实现跨域访问配置读取。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8274
|
|||
func (s *BucketService) GetCORS(ctx context.Context) (*BucketGetCORSResult, *Response, error) { |
|||
var res BucketGetCORSResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?cors", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// BucketPutCORSOptions ...
|
|||
type BucketPutCORSOptions struct { |
|||
XMLName xml.Name `xml:"CORSConfiguration"` |
|||
Rules []BucketCORSRule `xml:"CORSRule,omitempty"` |
|||
} |
|||
|
|||
// PutCORS ...
|
|||
//
|
|||
// Put Bucket CORS实现跨域访问设置,您可以通过传入XML格式的配置文件实现配置,文件大小限制为64 KB。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8279
|
|||
func (s *BucketService) PutCORS(ctx context.Context, opt *BucketPutCORSOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?cors", |
|||
method: http.MethodPut, |
|||
body: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// DeleteCORS ...
|
|||
//
|
|||
// Delete Bucket CORS实现跨域访问配置删除。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8283
|
|||
func (s *BucketService) DeleteCORS(ctx context.Context) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?cors", |
|||
method: http.MethodDelete, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
@ -0,0 +1,138 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_GetCORS(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodGet) |
|||
vs := values{ |
|||
"cors": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<?xml version='1.0' encoding='utf-8' ?> |
|||
<CORSConfiguration> |
|||
<CORSRule> |
|||
<AllowedOrigin>http://www.qq.com</AllowedOrigin>
|
|||
<AllowedMethod>PUT</AllowedMethod> |
|||
<AllowedMethod>GET</AllowedMethod> |
|||
<AllowedHeader>x-cos-meta-test</AllowedHeader> |
|||
<AllowedHeader>x-cos-xx</AllowedHeader> |
|||
<ExposeHeader>x-cos-meta-test1</ExposeHeader> |
|||
<MaxAgeSeconds>500</MaxAgeSeconds> |
|||
</CORSRule> |
|||
<CORSRule> |
|||
<ID>1234</ID> |
|||
<AllowedOrigin>http://www.baidu.com</AllowedOrigin>
|
|||
<AllowedOrigin>twitter.com</AllowedOrigin> |
|||
<AllowedMethod>PUT</AllowedMethod> |
|||
<AllowedMethod>GET</AllowedMethod> |
|||
<MaxAgeSeconds>500</MaxAgeSeconds> |
|||
</CORSRule> |
|||
</CORSConfiguration>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Bucket.GetCORS(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.GetCORS returned error: %v", err) |
|||
} |
|||
|
|||
want := &BucketGetCORSResult{ |
|||
XMLName: xml.Name{Local: "CORSConfiguration"}, |
|||
Rules: []BucketCORSRule{ |
|||
{ |
|||
AllowedOrigins: []string{"http://www.qq.com"}, |
|||
AllowedMethods: []string{"PUT", "GET"}, |
|||
AllowedHeaders: []string{"x-cos-meta-test", "x-cos-xx"}, |
|||
MaxAgeSeconds: 500, |
|||
ExposeHeaders: []string{"x-cos-meta-test1"}, |
|||
}, |
|||
{ |
|||
ID: "1234", |
|||
AllowedOrigins: []string{"http://www.baidu.com", "twitter.com"}, |
|||
AllowedMethods: []string{"PUT", "GET"}, |
|||
MaxAgeSeconds: 500, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.GetLifecycle returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestBucketService_PutCORS(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketPutCORSOptions{ |
|||
Rules: []BucketCORSRule{ |
|||
{ |
|||
AllowedOrigins: []string{"http://www.qq.com"}, |
|||
AllowedMethods: []string{"PUT", "GET"}, |
|||
AllowedHeaders: []string{"x-cos-meta-test", "x-cos-xx"}, |
|||
MaxAgeSeconds: 500, |
|||
ExposeHeaders: []string{"x-cos-meta-test1"}, |
|||
}, |
|||
{ |
|||
ID: "1234", |
|||
AllowedOrigins: []string{"http://www.baidu.com", "twitter.com"}, |
|||
AllowedMethods: []string{"PUT", "GET"}, |
|||
MaxAgeSeconds: 500, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(BucketPutCORSOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"cors": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
want := opt |
|||
want.XMLName = xml.Name{Local: "CORSConfiguration"} |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Bucket.PutCORS request body: %+v, want %+v", v, want) |
|||
} |
|||
|
|||
}) |
|||
|
|||
_, err := client.Bucket.PutCORS(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.PutCORS returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestBucketService_DeleteCORS(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodDelete) |
|||
vs := values{ |
|||
"cors": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
_, err := client.Bucket.DeleteCORS(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.DeleteCORS returned error: %v", err) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,107 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
// BucketLifecycleFilter ...
|
|||
type BucketLifecycleFilter struct { |
|||
Prefix string `xml:"Prefix,omitempty"` |
|||
} |
|||
|
|||
// BucketLifecycleExpiration ...
|
|||
type BucketLifecycleExpiration struct { |
|||
Date string `xml:"Date,omitempty"` |
|||
Days int `xml:"Days,omitempty"` |
|||
} |
|||
|
|||
// BucketLifecycleTransition ...
|
|||
type BucketLifecycleTransition struct { |
|||
Date string `xml:"Date,omitempty"` |
|||
Days int `xml:"Days,omitempty"` |
|||
StorageClass string |
|||
} |
|||
|
|||
// BucketLifecycleAbortIncompleteMultipartUpload ...
|
|||
type BucketLifecycleAbortIncompleteMultipartUpload struct { |
|||
DaysAfterInitiation string `xml:"DaysAfterInititation,omitempty"` |
|||
} |
|||
|
|||
// BucketLifecycleRule ...
|
|||
type BucketLifecycleRule struct { |
|||
ID string `xml:"ID,omitempty"` |
|||
Status string |
|||
Filter *BucketLifecycleFilter `xml:"Filter,omitempty"` |
|||
Transition *BucketLifecycleTransition `xml:"Transition,omitempty"` |
|||
Expiration *BucketLifecycleExpiration `xml:"Expiration,omitempty"` |
|||
AbortIncompleteMultipartUpload *BucketLifecycleAbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"` |
|||
} |
|||
|
|||
// BucketGetLifecycleResult ...
|
|||
type BucketGetLifecycleResult struct { |
|||
XMLName xml.Name `xml:"LifecycleConfiguration"` |
|||
Rules []BucketLifecycleRule `xml:"Rule,omitempty"` |
|||
} |
|||
|
|||
// GetLifecycle ...
|
|||
//
|
|||
// Get Bucket Lifecycle请求实现读取生命周期管理的配置。当配置不存在时,返回404 Not Found。
|
|||
//
|
|||
// (目前只支持华南园区)
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8278
|
|||
func (s *BucketService) GetLifecycle(ctx context.Context) (*BucketGetLifecycleResult, *Response, error) { |
|||
var res BucketGetLifecycleResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?lifecycle", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// BucketPutLifecycleOptions ...
|
|||
type BucketPutLifecycleOptions struct { |
|||
XMLName xml.Name `xml:"LifecycleConfiguration"` |
|||
Rules []BucketLifecycleRule `xml:"Rule,omitempty"` |
|||
} |
|||
|
|||
// PutLifecycle ...
|
|||
//
|
|||
// Put Bucket Lifecycle请求实现设置生命周期管理的功能。您可以通过该请求实现数据的生命周期管理配置和定期删除。
|
|||
//
|
|||
// 此请求为覆盖操作,上传新的配置文件将覆盖之前的配置文件。生命周期管理对文件和文件夹同时生效。
|
|||
//
|
|||
// (目前只支持华南园区)
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8280
|
|||
func (s *BucketService) PutLifecycle(ctx context.Context, opt *BucketPutLifecycleOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?lifecycle", |
|||
method: http.MethodPut, |
|||
body: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// DeleteLifecycle ...
|
|||
//
|
|||
// Delete Bucket Lifecycle请求实现删除生命周期管理。
|
|||
//
|
|||
// (目前只支持华南园区)
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8284
|
|||
func (s *BucketService) DeleteLifecycle(ctx context.Context) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?lifecycle", |
|||
method: http.MethodDelete, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
@ -0,0 +1,140 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_GetLifecycle(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodGet) |
|||
vs := values{ |
|||
"lifecycle": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<LifecycleConfiguration> |
|||
<Rule> |
|||
<ID>1234</ID> |
|||
<Filter> |
|||
<Prefix>test</Prefix> |
|||
</Filter> |
|||
<Status>Enabled</Status> |
|||
<Transition> |
|||
<Days>10</Days> |
|||
<StorageClass>Standard</StorageClass> |
|||
</Transition> |
|||
</Rule> |
|||
<Rule> |
|||
<ID>123422</ID> |
|||
<Filter> |
|||
<Prefix>gg</Prefix> |
|||
</Filter> |
|||
<Status>Disabled</Status> |
|||
<Expiration> |
|||
<Days>10</Days> |
|||
</Expiration> |
|||
</Rule> |
|||
</LifecycleConfiguration>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Bucket.GetLifecycle(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.GetLifecycle returned error: %v", err) |
|||
} |
|||
|
|||
want := &BucketGetLifecycleResult{ |
|||
XMLName: xml.Name{Local: "LifecycleConfiguration"}, |
|||
Rules: []BucketLifecycleRule{ |
|||
{ |
|||
ID: "1234", |
|||
Filter: &BucketLifecycleFilter{Prefix: "test"}, |
|||
Status: "Enabled", |
|||
Transition: &BucketLifecycleTransition{Days: 10, StorageClass: "Standard"}, |
|||
}, |
|||
{ |
|||
ID: "123422", |
|||
Filter: &BucketLifecycleFilter{Prefix: "gg"}, |
|||
Status: "Disabled", |
|||
Expiration: &BucketLifecycleExpiration{Days: 10}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.GetLifecycle returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestBucketService_PutLifecycle(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketPutLifecycleOptions{ |
|||
Rules: []BucketLifecycleRule{ |
|||
{ |
|||
ID: "1234", |
|||
Filter: &BucketLifecycleFilter{Prefix: "test"}, |
|||
Status: "Enabled", |
|||
Transition: &BucketLifecycleTransition{Days: 10, StorageClass: "Standard"}, |
|||
}, |
|||
{ |
|||
ID: "123422", |
|||
Filter: &BucketLifecycleFilter{Prefix: "gg"}, |
|||
Status: "Disabled", |
|||
Expiration: &BucketLifecycleExpiration{Days: 10}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(BucketPutLifecycleOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"lifecycle": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
want := opt |
|||
want.XMLName = xml.Name{Local: "LifecycleConfiguration"} |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Bucket.PutLifecycle request body: %+v, want %+v", v, want) |
|||
} |
|||
|
|||
}) |
|||
|
|||
_, err := client.Bucket.PutLifecycle(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.PutLifecycle returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestBucketService_DeleteLifecycle(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodDelete) |
|||
vs := values{ |
|||
"lifecycle": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
_, err := client.Bucket.DeleteLifecycle(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.DeleteLifecycle returned error: %v", err) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,30 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// BucketGetLocationResult ...
|
|||
type BucketGetLocationResult struct { |
|||
XMLName xml.Name `xml:"LocationConstraint"` |
|||
Location string `xml:",chardata"` |
|||
} |
|||
|
|||
// GetLocation ...
|
|||
//
|
|||
// Get Bucket Location接口获取Bucket所在地域信息,只有Bucket所有者有权限读取信息。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8275
|
|||
func (s *BucketService) GetLocation(ctx context.Context) (*BucketGetLocationResult, *Response, error) { |
|||
var res BucketGetLocationResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?location", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
@ -0,0 +1,39 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_GetLocation(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
vs := values{ |
|||
"location": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<?xml version='1.0' encoding='utf-8' ?> |
|||
<LocationConstraint>ap-guangzhou</LocationConstraint>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Bucket.GetLocation(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.GetLocation returned error: %v", err) |
|||
} |
|||
|
|||
want := &BucketGetLocationResult{ |
|||
XMLName: xml.Name{Local: "LocationConstraint"}, |
|||
Location: "ap-guangzhou", |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.GetLocation returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// ListMultipartUploadsResult ...
|
|||
type ListMultipartUploadsResult struct { |
|||
XMLName xml.Name `xml:"ListMultipartUploadsResult"` |
|||
Bucket string `xml:"Bucket"` |
|||
EncodingType string `xml:"Encoding-Type"` |
|||
KeyMarker string |
|||
UploadIDMarker string `xml:"UploadIdMarker"` |
|||
NextKeyMarker string |
|||
NextUploadIDMarker string `xml:"NextUploadIdMarker"` |
|||
MaxUploads int |
|||
IsTruncated bool |
|||
Uploads []struct { |
|||
Key string |
|||
UploadID string `xml:"UploadId"` |
|||
StorageClass string |
|||
Initiator *Initiator |
|||
Owner *Owner |
|||
Initiated string |
|||
} `xml:"Upload,omitempty"` |
|||
Prefix string |
|||
Delimiter string `xml:"delimiter,omitempty"` |
|||
CommonPrefixes []string `xml:"CommonPrefixs>Prefix,omitempty"` |
|||
} |
|||
|
|||
// ListMultipartUploadsOptions ...
|
|||
type ListMultipartUploadsOptions struct { |
|||
Delimiter string `url:"delimiter,omitempty"` |
|||
EncodingType string `url:"encoding-type,omitempty"` |
|||
Prefix string `url:"prefix,omitempty"` |
|||
MaxUploads int `url:"max-uploads,omitempty"` |
|||
KeyMarker string `url:"key-marker,omitempty"` |
|||
UploadIDMarker string `url:"upload-id-marker,omitempty"` |
|||
} |
|||
|
|||
// ListMultipartUploads ...
|
|||
//
|
|||
// List Multipart Uploads用来查询正在进行中的分块上传。单次最多列出1000个正在进行中的分块上传。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7736
|
|||
func (s *BucketService) ListMultipartUploads(ctx context.Context, opt *ListMultipartUploadsOptions) (*ListMultipartUploadsResult, *Response, error) { |
|||
var res ListMultipartUploadsResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?uploads", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
optQuery: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
@ -0,0 +1,121 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_ListMultipartUploads(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
vs := values{ |
|||
"uploads": "", |
|||
"prefix": "t", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<ListMultipartUploadsResult> |
|||
<Bucket>test-1253846586</Bucket> |
|||
<Encoding-Type/> |
|||
<KeyMarker/> |
|||
<UploadIdMarker/> |
|||
<MaxUploads>1000</MaxUploads> |
|||
<Prefix>t</Prefix> |
|||
<Delimiter>/</Delimiter> |
|||
<IsTruncated>false</IsTruncated> |
|||
<CommonPrefixs> |
|||
<Prefix>test/</Prefix> |
|||
</CommonPrefixs> |
|||
<Upload> |
|||
<Key>test_multipart.txt</Key> |
|||
<UploadId>14972623850a5de3f4f10605ab9f339c8bdf1b77e06f03fb981e7e76c86554b7bdb6072b36</UploadId> |
|||
<Initiator> |
|||
<ID>100000760461/100000760461</ID> |
|||
<DisplayName/> |
|||
</Initiator> |
|||
<Owner> |
|||
<ID>100000760461/100000760461</ID> |
|||
<DisplayName/> |
|||
</Owner> |
|||
<StorageClass>STANDARD</StorageClass> |
|||
<Initiated>2017-06-12T10:13:05.000Z</Initiated> |
|||
</Upload> |
|||
<Upload> |
|||
<Key>test_multipar2t.txt</Key> |
|||
<UploadId>1497515958744e899fc341bfbb995ebd57b395f63930411d855aaac1b5cd7d834a15442831</UploadId> |
|||
<Initiator> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>100000760461</DisplayName> |
|||
</Initiator> |
|||
<Owner> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>100000760461</DisplayName> |
|||
</Owner> |
|||
<StorageClass>STANDARD</StorageClass> |
|||
<Initiated>2017-06-15T08:39:18.000Z</Initiated> |
|||
</Upload> |
|||
</ListMultipartUploadsResult>`) |
|||
}) |
|||
|
|||
opt := &ListMultipartUploadsOptions{ |
|||
Prefix: "t", |
|||
} |
|||
ref, _, err := client.Bucket.ListMultipartUploads(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.ListMultipartUploads returned error: %v", err) |
|||
} |
|||
|
|||
want := &ListMultipartUploadsResult{ |
|||
XMLName: xml.Name{Local: "ListMultipartUploadsResult"}, |
|||
Bucket: "test-1253846586", |
|||
MaxUploads: 1000, |
|||
IsTruncated: false, |
|||
Uploads: []struct { |
|||
Key string |
|||
UploadID string `xml:"UploadId"` |
|||
StorageClass string |
|||
Initiator *Initiator |
|||
Owner *Owner |
|||
Initiated string |
|||
}{ |
|||
{ |
|||
Key: "test_multipart.txt", |
|||
UploadID: "14972623850a5de3f4f10605ab9f339c8bdf1b77e06f03fb981e7e76c86554b7bdb6072b36", |
|||
Initiator: &Initiator{ |
|||
ID: "100000760461/100000760461", |
|||
}, |
|||
Owner: &Owner{ |
|||
ID: "100000760461/100000760461", |
|||
}, |
|||
StorageClass: "STANDARD", |
|||
Initiated: "2017-06-12T10:13:05.000Z", |
|||
}, |
|||
{ |
|||
Key: "test_multipar2t.txt", |
|||
UploadID: "1497515958744e899fc341bfbb995ebd57b395f63930411d855aaac1b5cd7d834a15442831", |
|||
Initiator: &Initiator{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "100000760461", |
|||
}, |
|||
Owner: &Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "100000760461", |
|||
}, |
|||
StorageClass: "STANDARD", |
|||
Initiated: "2017-06-15T08:39:18.000Z", |
|||
}, |
|||
}, |
|||
Prefix: "t", |
|||
CommonPrefixes: []string{"test/"}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.ListMultipartUploads returned \n%+v, want \n%+v", ref, want) |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// BucketTaggingTag ...
|
|||
type BucketTaggingTag struct { |
|||
Key string |
|||
Value string |
|||
} |
|||
|
|||
// BucketGetTaggingResult ...
|
|||
type BucketGetTaggingResult struct { |
|||
XMLName xml.Name `xml:"Tagging"` |
|||
TagSet []BucketTaggingTag `xml:"TagSet>Tag,omitempty"` |
|||
} |
|||
|
|||
// GetTagging ...
|
|||
//
|
|||
// Get Bucket Tagging接口实现获取指定Bucket的标签。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8277
|
|||
func (s *BucketService) GetTagging(ctx context.Context) (*BucketGetTaggingResult, *Response, error) { |
|||
var res BucketGetTaggingResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?tagging", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// BucketPutTaggingOptions ...
|
|||
type BucketPutTaggingOptions struct { |
|||
XMLName xml.Name `xml:"Tagging"` |
|||
TagSet []BucketTaggingTag `xml:"TagSet>Tag,omitempty"` |
|||
} |
|||
|
|||
// PutTagging ...
|
|||
//
|
|||
// Put Bucket Tagging接口实现给用指定Bucket打标签。用来组织和管理相关Bucket。
|
|||
//
|
|||
// 当该请求设置相同Key名称,不同Value时,会返回400。请求成功,则返回204。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8281
|
|||
func (s *BucketService) PutTagging(ctx context.Context, opt *BucketPutTaggingOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?tagging", |
|||
method: http.MethodPut, |
|||
body: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// DeleteTagging ...
|
|||
//
|
|||
// Delete Bucket Tagging接口实现删除指定Bucket的标签。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8286
|
|||
func (s *BucketService) DeleteTagging(ctx context.Context) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?tagging", |
|||
method: http.MethodDelete, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
@ -0,0 +1,115 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_GetTagging(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
vs := values{ |
|||
"tagging": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<Tagging> |
|||
<TagSet> |
|||
<Tag> |
|||
<Key>test_k2</Key> |
|||
<Value>test_v2</Value> |
|||
</Tag> |
|||
<Tag> |
|||
<Key>test_k3</Key> |
|||
<Value>test_vv</Value> |
|||
</Tag> |
|||
</TagSet> |
|||
</Tagging>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Bucket.GetTagging(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.GetTagging returned error: %v", err) |
|||
} |
|||
|
|||
want := &BucketGetTaggingResult{ |
|||
XMLName: xml.Name{Local: "Tagging"}, |
|||
TagSet: []BucketTaggingTag{ |
|||
{"test_k2", "test_v2"}, |
|||
{"test_k3", "test_vv"}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.GetTagging returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestBucketService_PutTagging(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketPutTaggingOptions{ |
|||
TagSet: []BucketTaggingTag{ |
|||
{ |
|||
Key: "test_k2", |
|||
Value: "test_v2", |
|||
}, |
|||
{ |
|||
Key: "test_k3", |
|||
Value: "test_v3", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(BucketPutTaggingOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, "PUT") |
|||
vs := values{ |
|||
"tagging": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
want := opt |
|||
want.XMLName = xml.Name{Local: "Tagging"} |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Bucket.PutTagging request body: %+v, want %+v", v, want) |
|||
} |
|||
|
|||
}) |
|||
|
|||
_, err := client.Bucket.PutTagging(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.PutTagging returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestBucketService_DeleteTagging(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodDelete) |
|||
vs := values{ |
|||
"tagging": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
_, err := client.Bucket.DeleteTagging(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.DeleteTagging returned error: %v", err) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,152 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestBucketService_Get(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketGetOptions{ |
|||
Prefix: "test", |
|||
MaxKeys: 2, |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodGet) |
|||
vs := values{ |
|||
"prefix": "test", |
|||
"max-keys": "2", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
fmt.Fprint(w, `<?xml version='1.0' encoding='utf-8' ?> |
|||
<ListBucketResult> |
|||
<Name>test-1253846586</Name> |
|||
<Prefix>test</Prefix> |
|||
<Marker/> |
|||
<MaxKeys>2</MaxKeys> |
|||
<IsTruncated>true</IsTruncated> |
|||
<NextMarker>test/delete.txt</NextMarker> |
|||
<Contents> |
|||
<Key>test/</Key> |
|||
<LastModified>2017-06-09T16:32:25.000Z</LastModified> |
|||
<ETag>""</ETag> |
|||
<Size>0</Size> |
|||
<Owner> |
|||
<ID>1253846586</ID> |
|||
</Owner> |
|||
<StorageClass>STANDARD</StorageClass> |
|||
</Contents> |
|||
<Contents> |
|||
<Key>test/anonymous_get.go</Key> |
|||
<LastModified>2017-06-17T15:09:26.000Z</LastModified> |
|||
<ETag>"5b7236085f08b3818bfa40b03c946dcc"</ETag> |
|||
<Size>8</Size> |
|||
<Owner> |
|||
<ID>1253846586</ID> |
|||
</Owner> |
|||
<StorageClass>STANDARD</StorageClass> |
|||
</Contents> |
|||
</ListBucketResult>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Bucket.Get(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.Get returned error: %v", err) |
|||
} |
|||
|
|||
want := &BucketGetResult{ |
|||
XMLName: xml.Name{Local: "ListBucketResult"}, |
|||
Name: "test-1253846586", |
|||
Prefix: "test", |
|||
MaxKeys: 2, |
|||
IsTruncated: true, |
|||
NextMarker: "test/delete.txt", |
|||
Contents: []Object{ |
|||
{ |
|||
Key: "test/", |
|||
LastModified: "2017-06-09T16:32:25.000Z", |
|||
ETag: "\"\"", |
|||
Size: 0, |
|||
Owner: &Owner{ |
|||
ID: "1253846586", |
|||
}, |
|||
StorageClass: "STANDARD", |
|||
}, |
|||
{ |
|||
Key: "test/anonymous_get.go", |
|||
LastModified: "2017-06-17T15:09:26.000Z", |
|||
ETag: "\"5b7236085f08b3818bfa40b03c946dcc\"", |
|||
Size: 8, |
|||
Owner: &Owner{ |
|||
ID: "1253846586", |
|||
}, |
|||
StorageClass: "STANDARD", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Bucket.Get returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestBucketService_Put(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &BucketPutOptions{ |
|||
XCosACL: "public-read", |
|||
} |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(BucketPutTaggingOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, "PUT") |
|||
testHeader(t, r, "x-cos-acl", "public-read") |
|||
}) |
|||
|
|||
_, err := client.Bucket.Put(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.Put returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestBucketService_Delete(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodDelete) |
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
_, err := client.Bucket.Delete(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.Delete returned error: %v", err) |
|||
} |
|||
} |
|||
|
|||
func TestBucketService_Head(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodHead) |
|||
w.WriteHeader(http.StatusOK) |
|||
}) |
|||
|
|||
_, err := client.Bucket.Head(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Bucket.Head returned error: %v", err) |
|||
} |
|||
} |
@ -0,0 +1,352 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"bytes" |
|||
"context" |
|||
"encoding/base64" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"io" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"net/url" |
|||
"reflect" |
|||
"text/template" |
|||
|
|||
"strconv" |
|||
|
|||
"github.com/google/go-querystring/query" |
|||
"github.com/tencentyun/go-httpheader" |
|||
) |
|||
|
|||
const ( |
|||
// Version ...
|
|||
Version = "0.7.3" |
|||
userAgent = "cos-go-sdk-v5/" + Version |
|||
contentTypeXML = "application/xml" |
|||
defaultServiceBaseURL = "https://service.cos.myqcloud.com" |
|||
) |
|||
|
|||
var bucketURLTemplate = template.Must( |
|||
template.New("bucketURLFormat").Parse( |
|||
"{{.Schema}}://{{.BucketName}}.cos.{{.Region}}.myqcloud.com", |
|||
), |
|||
) |
|||
|
|||
// BaseURL 访问各 API 所需的基础 URL
|
|||
type BaseURL struct { |
|||
// 访问 bucket, object 相关 API 的基础 URL(不包含 path 部分): http://example.com
|
|||
BucketURL *url.URL |
|||
// 访问 service API 的基础 URL(不包含 path 部分): http://example.com
|
|||
ServiceURL *url.URL |
|||
} |
|||
|
|||
// NewBucketURL 生成 BaseURL 所需的 BucketURL
|
|||
//
|
|||
// bucketName: bucket名称, bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
|
|||
// Region: 区域代码: ap-beijing-1,ap-beijing,ap-shanghai,ap-guangzhou...
|
|||
// secure: 是否使用 https
|
|||
func NewBucketURL(bucketName, region string, secure bool) *url.URL { |
|||
schema := "https" |
|||
if !secure { |
|||
schema = "http" |
|||
} |
|||
|
|||
w := bytes.NewBuffer(nil) |
|||
bucketURLTemplate.Execute(w, struct { |
|||
Schema string |
|||
BucketName string |
|||
Region string |
|||
}{ |
|||
schema, bucketName, region, |
|||
}) |
|||
|
|||
u, _ := url.Parse(w.String()) |
|||
return u |
|||
} |
|||
|
|||
// A Client manages communication with the COS API.
|
|||
type Client struct { |
|||
client *http.Client |
|||
|
|||
UserAgent string |
|||
BaseURL *BaseURL |
|||
|
|||
common service |
|||
|
|||
Service *ServiceService |
|||
Bucket *BucketService |
|||
Object *ObjectService |
|||
} |
|||
|
|||
type service struct { |
|||
client *Client |
|||
} |
|||
|
|||
// NewClient returns a new COS API client.
|
|||
func NewClient(uri *BaseURL, httpClient *http.Client) *Client { |
|||
if httpClient == nil { |
|||
httpClient = &http.Client{} |
|||
} |
|||
|
|||
baseURL := &BaseURL{} |
|||
if uri != nil { |
|||
baseURL.BucketURL = uri.BucketURL |
|||
baseURL.ServiceURL = uri.ServiceURL |
|||
} |
|||
if baseURL.ServiceURL == nil { |
|||
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL) |
|||
} |
|||
|
|||
c := &Client{ |
|||
client: httpClient, |
|||
UserAgent: userAgent, |
|||
BaseURL: baseURL, |
|||
} |
|||
c.common.client = c |
|||
c.Service = (*ServiceService)(&c.common) |
|||
c.Bucket = (*BucketService)(&c.common) |
|||
c.Object = (*ObjectService)(&c.common) |
|||
return c |
|||
} |
|||
|
|||
func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) { |
|||
uri, err = addURLOptions(uri, optQuery) |
|||
if err != nil { |
|||
return |
|||
} |
|||
u, _ := url.Parse(uri) |
|||
urlStr := baseURL.ResolveReference(u).String() |
|||
|
|||
var reader io.Reader |
|||
contentType := "" |
|||
contentMD5 := "" |
|||
xsha1 := "" |
|||
if body != nil { |
|||
// 上传文件
|
|||
if r, ok := body.(io.Reader); ok { |
|||
reader = r |
|||
} else { |
|||
b, err := xml.Marshal(body) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
contentType = contentTypeXML |
|||
reader = bytes.NewReader(b) |
|||
contentMD5 = base64.StdEncoding.EncodeToString(calMD5Digest(b)) |
|||
//xsha1 = base64.StdEncoding.EncodeToString(calSHA1Digest(b))
|
|||
} |
|||
} else { |
|||
contentType = contentTypeXML |
|||
} |
|||
|
|||
req, err = http.NewRequest(method, urlStr, reader) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
req.Header, err = addHeaderOptions(req.Header, optHeader) |
|||
if err != nil { |
|||
return |
|||
} |
|||
if v := req.Header.Get("Content-Length"); req.ContentLength == 0 && v != "" && v != "0" { |
|||
req.ContentLength, _ = strconv.ParseInt(v, 10, 64) |
|||
} |
|||
|
|||
if contentMD5 != "" { |
|||
req.Header["Content-MD5"] = []string{contentMD5} |
|||
} |
|||
if xsha1 != "" { |
|||
req.Header.Set("x-cos-sha1", xsha1) |
|||
} |
|||
if c.UserAgent != "" { |
|||
req.Header.Set("User-Agent", c.UserAgent) |
|||
} |
|||
if req.Header.Get("Content-Type") == "" && contentType != "" { |
|||
req.Header.Set("Content-Type", contentType) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{}, closeBody bool) (*Response, error) { |
|||
req = req.WithContext(ctx) |
|||
|
|||
resp, err := c.client.Do(req) |
|||
if err != nil { |
|||
// If we got an error, and the context has been canceled,
|
|||
// the context's error is probably more useful.
|
|||
select { |
|||
case <-ctx.Done(): |
|||
return nil, ctx.Err() |
|||
default: |
|||
} |
|||
return nil, err |
|||
} |
|||
|
|||
defer func() { |
|||
if closeBody { |
|||
// Close the body to let the Transport reuse the connection
|
|||
io.Copy(ioutil.Discard, resp.Body) |
|||
resp.Body.Close() |
|||
} |
|||
}() |
|||
|
|||
response := newResponse(resp) |
|||
|
|||
err = checkResponse(resp) |
|||
if err != nil { |
|||
// even though there was an error, we still return the response
|
|||
// in case the caller wants to inspect it further
|
|||
return response, err |
|||
} |
|||
|
|||
if result != nil { |
|||
if w, ok := result.(io.Writer); ok { |
|||
io.Copy(w, resp.Body) |
|||
} else { |
|||
err = xml.NewDecoder(resp.Body).Decode(result) |
|||
if err == io.EOF { |
|||
err = nil // ignore EOF errors caused by empty response body
|
|||
} |
|||
} |
|||
} |
|||
|
|||
return response, err |
|||
} |
|||
|
|||
type sendOptions struct { |
|||
// 基础 URL
|
|||
baseURL *url.URL |
|||
// URL 中除基础 URL 外的剩余部分
|
|||
uri string |
|||
// 请求方法
|
|||
method string |
|||
|
|||
body interface{} |
|||
// url 查询参数
|
|||
optQuery interface{} |
|||
// http header 参数
|
|||
optHeader interface{} |
|||
// 用 result 反序列化 resp.Body
|
|||
result interface{} |
|||
// 是否禁用自动调用 resp.Body.Close()
|
|||
// 自动调用 Close() 是为了能够重用连接
|
|||
disableCloseBody bool |
|||
} |
|||
|
|||
func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, err error) { |
|||
req, err := c.newRequest(ctx, opt.baseURL, opt.uri, opt.method, opt.body, opt.optQuery, opt.optHeader) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody) |
|||
if err != nil { |
|||
return |
|||
} |
|||
return |
|||
} |
|||
|
|||
// addURLOptions adds the parameters in opt as URL query parameters to s. opt
|
|||
// must be a struct whose fields may contain "url" tags.
|
|||
func addURLOptions(s string, opt interface{}) (string, error) { |
|||
v := reflect.ValueOf(opt) |
|||
if v.Kind() == reflect.Ptr && v.IsNil() { |
|||
return s, nil |
|||
} |
|||
|
|||
u, err := url.Parse(s) |
|||
if err != nil { |
|||
return s, err |
|||
} |
|||
|
|||
qs, err := query.Values(opt) |
|||
if err != nil { |
|||
return s, err |
|||
} |
|||
|
|||
// 保留原有的参数,并且放在前面。因为 cos 的 url 路由是以第一个参数作为路由的
|
|||
// e.g. /?uploads
|
|||
q := u.RawQuery |
|||
rq := qs.Encode() |
|||
if q != "" { |
|||
if rq != "" { |
|||
u.RawQuery = fmt.Sprintf("%s&%s", q, qs.Encode()) |
|||
} |
|||
} else { |
|||
u.RawQuery = rq |
|||
} |
|||
return u.String(), nil |
|||
} |
|||
|
|||
// addHeaderOptions adds the parameters in opt as Header fields to req. opt
|
|||
// must be a struct whose fields may contain "header" tags.
|
|||
func addHeaderOptions(header http.Header, opt interface{}) (http.Header, error) { |
|||
v := reflect.ValueOf(opt) |
|||
if v.Kind() == reflect.Ptr && v.IsNil() { |
|||
return header, nil |
|||
} |
|||
|
|||
h, err := httpheader.Header(opt) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
for key, values := range h { |
|||
for _, value := range values { |
|||
header.Add(key, value) |
|||
} |
|||
} |
|||
return header, nil |
|||
} |
|||
|
|||
// Owner ...
|
|||
type Owner struct { |
|||
UIN string `xml:"uin,omitempty"` |
|||
ID string `xml:",omitempty"` |
|||
DisplayName string `xml:",omitempty"` |
|||
} |
|||
|
|||
// Initiator ...
|
|||
type Initiator Owner |
|||
|
|||
// Response API 响应
|
|||
type Response struct { |
|||
*http.Response |
|||
} |
|||
|
|||
func newResponse(resp *http.Response) *Response { |
|||
return &Response{ |
|||
Response: resp, |
|||
} |
|||
} |
|||
|
|||
// ACLHeaderOptions ...
|
|||
type ACLHeaderOptions struct { |
|||
XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"` |
|||
XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"` |
|||
XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"` |
|||
XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"` |
|||
} |
|||
|
|||
// ACLGrantee ...
|
|||
type ACLGrantee struct { |
|||
Type string `xml:"type,attr"` |
|||
UIN string `xml:"uin,omitempty"` |
|||
ID string `xml:",omitempty"` |
|||
DisplayName string `xml:",omitempty"` |
|||
SubAccount string `xml:"Subaccount,omitempty"` |
|||
} |
|||
|
|||
// ACLGrant ...
|
|||
type ACLGrant struct { |
|||
Grantee *ACLGrantee |
|||
Permission string |
|||
} |
|||
|
|||
// ACLXml ...
|
|||
type ACLXml struct { |
|||
XMLName xml.Name `xml:"AccessControlPolicy"` |
|||
Owner *Owner |
|||
AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"` |
|||
} |
@ -0,0 +1,156 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/xml" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"net/http/httptest" |
|||
"net/url" |
|||
"reflect" |
|||
"testing" |
|||
"time" |
|||
) |
|||
|
|||
var ( |
|||
// mux is the HTTP request multiplexer used with the test server.
|
|||
mux *http.ServeMux |
|||
|
|||
// client is the COS client being tested.
|
|||
client *Client |
|||
|
|||
// server is a test HTTP server used to provide mock API responses.
|
|||
server *httptest.Server |
|||
) |
|||
|
|||
// setup sets up a test HTTP server along with a cos.Client that is
|
|||
// configured to talk to that test server. Tests should register handlers on
|
|||
// mux which provide mock responses for the API method being tested.
|
|||
func setup() { |
|||
// test server
|
|||
mux = http.NewServeMux() |
|||
server = httptest.NewServer(mux) |
|||
|
|||
u, _ := url.Parse(server.URL) |
|||
client = NewClient(&BaseURL{u, u}, nil) |
|||
} |
|||
|
|||
// teardown closes the test HTTP server.
|
|||
func teardown() { |
|||
server.Close() |
|||
} |
|||
|
|||
type values map[string]string |
|||
|
|||
func testFormValues(t *testing.T, r *http.Request, values values) { |
|||
want := url.Values{} |
|||
for k, v := range values { |
|||
want.Set(k, v) |
|||
} |
|||
|
|||
r.ParseForm() |
|||
if got := r.Form; !reflect.DeepEqual(got, want) { |
|||
t.Errorf("Request parameters: %v, want %v", got, want) |
|||
} |
|||
} |
|||
|
|||
func testMethod(t *testing.T, r *http.Request, want string) { |
|||
if got := r.Method; got != want { |
|||
t.Errorf("Request method: %v, want %v", got, want) |
|||
} |
|||
} |
|||
|
|||
func testHeader(t *testing.T, r *http.Request, header string, want string) { |
|||
if got := r.Header.Get(header); got != want { |
|||
t.Errorf("Header.Get(%q) returned %q, want %q", header, got, want) |
|||
} |
|||
} |
|||
|
|||
func testURLParseError(t *testing.T, err error) { |
|||
if err == nil { |
|||
t.Errorf("Expected error to be returned") |
|||
} |
|||
if err, ok := err.(*url.Error); !ok || err.Op != "parse" { |
|||
t.Errorf("Expected URL parse error, got %+v", err) |
|||
} |
|||
} |
|||
|
|||
func testBody(t *testing.T, r *http.Request, want string) { |
|||
b, err := ioutil.ReadAll(r.Body) |
|||
if err != nil { |
|||
t.Errorf("Error reading request body: %v", err) |
|||
} |
|||
if got := string(b); got != want { |
|||
t.Errorf("request Body is %s, want %s", got, want) |
|||
} |
|||
} |
|||
|
|||
// Helper function to test that a value is marshalled to XML as expected.
|
|||
func testXMLMarshal(t *testing.T, v interface{}, want string) { |
|||
j, err := xml.Marshal(v) |
|||
if err != nil { |
|||
t.Errorf("Unable to marshal JSON for %v", v) |
|||
} |
|||
|
|||
w := new(bytes.Buffer) |
|||
err = xml.NewEncoder(w).Encode([]byte(want)) |
|||
if err != nil { |
|||
t.Errorf("String is not valid json: %s", want) |
|||
} |
|||
|
|||
if w.String() != string(j) { |
|||
t.Errorf("xml.Marshal(%q) returned %s, want %s", v, j, w) |
|||
} |
|||
|
|||
// now go the other direction and make sure things unmarshal as expected
|
|||
u := reflect.ValueOf(v).Interface() |
|||
if err := xml.Unmarshal([]byte(want), u); err != nil { |
|||
t.Errorf("Unable to unmarshal XML for %v", want) |
|||
} |
|||
|
|||
if !reflect.DeepEqual(v, u) { |
|||
t.Errorf("xml.Unmarshal(%q) returned %s, want %s", want, u, v) |
|||
} |
|||
} |
|||
|
|||
func TestNewClient(t *testing.T) { |
|||
c := NewClient(nil, nil) |
|||
|
|||
if got, want := c.BaseURL.ServiceURL.String(), defaultServiceBaseURL; got != want { |
|||
t.Errorf("NewClient BaseURL is %v, want %v", got, want) |
|||
} |
|||
if got, want := c.UserAgent, userAgent; got != want { |
|||
t.Errorf("NewClient UserAgent is %v, want %v", got, want) |
|||
} |
|||
} |
|||
|
|||
func TestNewBucketURL_secure_false(t *testing.T) { |
|||
got := NewBucketURL("bname-idx", "ap-guangzhou", false).String() |
|||
want := "http://bname-idx.cos.ap-guangzhou.myqcloud.com" |
|||
if got != want { |
|||
t.Errorf("NewBucketURL is %v, want %v", got, want) |
|||
} |
|||
} |
|||
|
|||
func TestNewBucketURL_secure_true(t *testing.T) { |
|||
got := NewBucketURL("bname-idx", "ap-guangzhou", true).String() |
|||
want := "https://bname-idx.cos.ap-guangzhou.myqcloud.com" |
|||
if got != want { |
|||
t.Errorf("NewBucketURL is %v, want %v", got, want) |
|||
} |
|||
} |
|||
|
|||
func TestClient_doAPI(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
} |
|||
|
|||
func TestNewAuthTime(t *testing.T) { |
|||
a := NewAuthTime(time.Hour) |
|||
if a.SignStartTime != a.KeyStartTime || |
|||
a.SignEndTime != a.SignEndTime || |
|||
a.SignStartTime.Add(time.Hour) != a.SignEndTime { |
|||
t.Errorf("NewAuthTime request got %+v is not valid", a) |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
package debug |
|||
|
|||
import ( |
|||
"fmt" |
|||
"io" |
|||
"net/http" |
|||
"net/http/httputil" |
|||
"os" |
|||
) |
|||
|
|||
// DebugRequestTransport 会打印请求和响应信息, 方便调试.
|
|||
type DebugRequestTransport struct { |
|||
RequestHeader bool |
|||
RequestBody bool // RequestHeader 为 true 时,这个选项才会生效
|
|||
ResponseHeader bool |
|||
ResponseBody bool // ResponseHeader 为 true 时,这个选项才会生效
|
|||
|
|||
// debug 信息输出到 Writer 中, 默认是 os.Stderr
|
|||
Writer io.Writer |
|||
|
|||
Transport http.RoundTripper |
|||
} |
|||
|
|||
// RoundTrip implements the RoundTripper interface.
|
|||
func (t *DebugRequestTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
|||
req = cloneRequest(req) // per RoundTrip contract
|
|||
w := t.Writer |
|||
if w == nil { |
|||
w = os.Stderr |
|||
} |
|||
|
|||
if t.RequestHeader { |
|||
a, _ := httputil.DumpRequestOut(req, t.RequestBody) |
|||
fmt.Fprintf(w, "%s\n\n", string(a)) |
|||
} |
|||
|
|||
resp, err := t.transport().RoundTrip(req) |
|||
if err != nil { |
|||
return resp, err |
|||
} |
|||
|
|||
if t.ResponseHeader { |
|||
|
|||
b, _ := httputil.DumpResponse(resp, t.ResponseBody) |
|||
fmt.Fprintf(w, "%s\n", string(b)) |
|||
} |
|||
|
|||
return resp, err |
|||
} |
|||
|
|||
func (t *DebugRequestTransport) transport() http.RoundTripper { |
|||
if t.Transport != nil { |
|||
return t.Transport |
|||
} |
|||
return http.DefaultTransport |
|||
} |
|||
|
|||
// cloneRequest returns a clone of the provided *http.Request. The clone is a
|
|||
// shallow copy of the struct and its Header map.
|
|||
func cloneRequest(r *http.Request) *http.Request { |
|||
// shallow copy of the struct
|
|||
r2 := new(http.Request) |
|||
*r2 = *r |
|||
// deep copy of the Header
|
|||
r2.Header = make(http.Header, len(r.Header)) |
|||
for k, s := range r.Header { |
|||
r2.Header[k] = append([]string(nil), s...) |
|||
} |
|||
return r2 |
|||
} |
@ -0,0 +1,78 @@ |
|||
package debug |
|||
|
|||
import ( |
|||
"bytes" |
|||
"net/http" |
|||
"net/http/httptest" |
|||
"strings" |
|||
"testing" |
|||
) |
|||
|
|||
var ( |
|||
// mux is the HTTP request multiplexer used with the test server.
|
|||
mux *http.ServeMux |
|||
|
|||
// server is a test HTTP server used to provide mock API responses.
|
|||
server *httptest.Server |
|||
) |
|||
|
|||
// setup sets up a test HTTP server along with a cos.Client that is
|
|||
// configured to talk to that test server. Tests should register handlers on
|
|||
// mux which provide mock responses for the API method being tested.
|
|||
func setup() { |
|||
// test server
|
|||
mux = http.NewServeMux() |
|||
server = httptest.NewServer(mux) |
|||
} |
|||
|
|||
// teardown closes the test HTTP server.
|
|||
func teardown() { |
|||
server.Close() |
|||
} |
|||
|
|||
func TestDebugRequestTransport(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
w.Header().Add("X-Test-Response", "2333") |
|||
w.WriteHeader(http.StatusBadGateway) |
|||
w.Write([]byte("test response body")) |
|||
}) |
|||
|
|||
w := bytes.NewBufferString("") |
|||
client := http.Client{} |
|||
|
|||
client.Transport = &DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
Writer: w, |
|||
} |
|||
|
|||
body := bytes.NewReader([]byte("test_request body")) |
|||
req, _ := http.NewRequest("GET", server.URL, body) |
|||
req.Header.Add("X-Test-Debug", "123") |
|||
client.Do(req) |
|||
|
|||
b := make([]byte, 800) |
|||
w.Read(b) |
|||
info := string(b) |
|||
if !strings.Contains(info, "GET / HTTP/1.1\r\n") || |
|||
!strings.Contains(info, "X-Test-Debug: 123\r\n") { |
|||
t.Errorf("DebugRequestTransport debug info %#v don't contains request header", info) |
|||
} |
|||
if !strings.Contains(info, "\r\n\r\ntest_request body") { |
|||
t.Errorf("DebugRequestTransport debug info %#v don't contains request body", info) |
|||
} |
|||
|
|||
if !strings.Contains(info, "HTTP/1.1 502 Bad Gateway\r\n") || |
|||
!strings.Contains(info, "X-Test-Response: 2333\r\n") { |
|||
t.Errorf("DebugRequestTransport debug info %#v don't contains response header", info) |
|||
} |
|||
|
|||
if !strings.Contains(info, "\r\n\r\ntest response body") { |
|||
t.Errorf("DebugRequestTransport debug info %#v don't contains response body", info) |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
/* |
|||
Package cos 腾讯云对象存储服务 COS(Cloud Object Storage) Go SDK。 |
|||
|
|||
|
|||
COS API Version |
|||
|
|||
封装了 V5 版本的 XML API 。 |
|||
|
|||
|
|||
Usage |
|||
|
|||
在项目的 example 目录下有各个 API 的使用示例 。 |
|||
|
|||
|
|||
Authentication |
|||
|
|||
默认所有 API 都是匿名访问. 如果想添加认证信息的话,可以通过自定义一个 http.Client 来添加认证信息. |
|||
|
|||
比如, 使用内置的 AuthorizationTransport 来为请求增加 Authorization Header 签名信息: |
|||
|
|||
client := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: "COS_SECRETID", |
|||
SecretKey: "COS_SECRETKEY", |
|||
}, |
|||
}) |
|||
|
|||
*/ |
|||
package cos |
@ -0,0 +1,49 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"encoding/xml" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net/http" |
|||
) |
|||
|
|||
// ErrorResponse 包含 API 返回的错误信息
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7730
|
|||
type ErrorResponse struct { |
|||
XMLName xml.Name `xml:"Error"` |
|||
Response *http.Response `xml:"-"` |
|||
Code string |
|||
Message string |
|||
Resource string |
|||
RequestID string `header:"x-cos-request-id,omitempty" url:"-" xml:"-"` |
|||
TraceID string `xml:"TraceId,omitempty"` |
|||
} |
|||
|
|||
// Error ...
|
|||
func (r *ErrorResponse) Error() string { |
|||
RequestID := r.RequestID |
|||
if (RequestID == "") { |
|||
RequestID = r.Response.Header["X-Cos-Request-Id"][0] |
|||
} |
|||
TraceID := r.TraceID |
|||
if (TraceID == "") { |
|||
TraceID = r.Response.Header["X-Cos-Trace-Id"][0] |
|||
} |
|||
return fmt.Sprintf("%v %v: %d %v(Message: %v, RequestId: %v, TraceId: %v)", |
|||
r.Response.Request.Method, r.Response.Request.URL, |
|||
r.Response.StatusCode, r.Code, r.Message, RequestID, TraceID) |
|||
} |
|||
|
|||
// 检查 response 是否是出错时的返回的 response
|
|||
func checkResponse(r *http.Response) error { |
|||
if c := r.StatusCode; 200 <= c && c <= 299 { |
|||
return nil |
|||
} |
|||
errorResponse := &ErrorResponse{Response: r} |
|||
data, err := ioutil.ReadAll(r.Body) |
|||
if err == nil && data != nil { |
|||
xml.Unmarshal(data, errorResponse) |
|||
} |
|||
return errorResponse |
|||
} |
@ -0,0 +1,56 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"fmt" |
|||
"net/http" |
|||
"testing" |
|||
) |
|||
|
|||
// func Test_checkResponse_error(t *testing.T) {
|
|||
// setup()
|
|||
// defer teardown()
|
|||
|
|||
// mux.HandleFunc("/test_409", func(w http.ResponseWriter, r *http.Request) {
|
|||
// w.WriteHeader(http.StatusConflict)
|
|||
// fmt.Fprint(w, `<?xml version='1.0' encoding='utf-8' ?>
|
|||
// <Error>
|
|||
// <Code>BucketAlreadyExists</Code>
|
|||
// <Message>The requested bucket name is not available.</Message>
|
|||
// <Resource>testdelete-1253846586.cos.ap-guangzhou.myqcloud.com</Resource>
|
|||
// <RequestId>NTk0NTRjZjZfNTViMjM1XzlkMV9hZTZh</RequestId>
|
|||
// <TraceId>OGVmYzZiMmQzYjA2OWNhODk0NTRkMTBiOWVmMDAxODc0OWRkZjk0ZDM1NmI1M2E2MTRlY2MzZDhmNmI5MWI1OTBjYzE2MjAxN2M1MzJiOTdkZjMxMDVlYTZjN2FiMmI0NTk3NWFiNjAyMzdlM2RlMmVmOGNiNWIxYjYwNDFhYmQ=</TraceId>
|
|||
// </Error>`)
|
|||
// })
|
|||
|
|||
// req, _ := http.NewRequest("GET", client.BaseURL.ServiceURL.String()+"/test_409", nil)
|
|||
// resp, _ := client.client.Do(req)
|
|||
// err := checkResponse(resp)
|
|||
|
|||
// if e, ok := err.(*ErrorResponse); ok {
|
|||
// if e.Error() == "" {
|
|||
// t.Errorf("Expected e.Error() not empty, got %+v", e.Error())
|
|||
// }
|
|||
// if e.Code != "BucketAlreadyExists" {
|
|||
// t.Errorf("Expected BucketAlreadyExists error, got %+v", e.Code)
|
|||
// }
|
|||
// } else {
|
|||
// t.Errorf("Expected ErrorResponse error, got %+v", err)
|
|||
// }
|
|||
// }
|
|||
|
|||
func Test_checkResponse_no_error(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/test_200", func(w http.ResponseWriter, r *http.Request) { |
|||
fmt.Fprint(w, `test`) |
|||
}) |
|||
|
|||
req, _ := http.NewRequest("GET", client.BaseURL.ServiceURL.String()+"/test_200", nil) |
|||
resp, _ := client.client.Do(req) |
|||
err := checkResponse(resp) |
|||
|
|||
if err != nil { |
|||
t.Errorf("Expected error == nil, got %+v", err) |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
|
|||
|
|||
``` |
|||
export COS_SECRETID=xx |
|||
export COS_SECRETKEY=xxx |
|||
|
|||
go run xxx.go |
|||
``` |
@ -0,0 +1,36 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://testdelete-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
_, err := c.Bucket.Delete(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
_, err := c.Bucket.DeleteCORS(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
_, err := c.Bucket.DeleteLifecycle(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
_, err := c.Bucket.DeleteTagging(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"os" |
|||
|
|||
"net/url" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
opt := &cos.BucketGetOptions{ |
|||
Prefix: "test", |
|||
MaxKeys: 3, |
|||
} |
|||
v, _, err := c.Bucket.Get(context.Background(), opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
for _, c := range v.Contents { |
|||
fmt.Printf("%s, %d\n", c.Key, c.Size) |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
v, _, err := c.Bucket.GetACL(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
for _, a := range v.AccessControlList { |
|||
fmt.Printf("%s, %s, %s\n", a.Grantee.Type, a.Grantee.ID, a.Permission) |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
v, _, err := c.Bucket.GetCORS(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
for _, r := range v.Rules { |
|||
|
|||
fmt.Printf("%s, %s\n", r.AllowedOrigins, r.AllowedMethods) |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
v, _, err := c.Bucket.GetLifecycle(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
for _, r := range v.Rules { |
|||
fmt.Printf("%s, %s\n", r.Filter.Prefix, r.Status) |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
v, _, err := c.Bucket.GetLocation(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", v.Location) |
|||
} |
@ -0,0 +1,40 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
v, _, err := c.Bucket.GetTagging(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
for _, t := range v.TagSet { |
|||
fmt.Printf("%s: %s\n", t.Key, t.Value) |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
resp, err := c.Bucket.Head(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Println(resp.Status) |
|||
} |
@ -0,0 +1,43 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
opt := &cos.ListMultipartUploadsOptions{ |
|||
Prefix: "t", |
|||
} |
|||
v, _, err := c.Bucket.ListMultipartUploads(context.Background(), opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
for _, p := range v.Uploads { |
|||
fmt.Printf("%s\n", p.Key) |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://testdelete-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
//opt := &cos.BucketPutOptions{
|
|||
// XCosACL: "public-read",
|
|||
//}
|
|||
_, err := c.Bucket.Put(context.Background(), nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,65 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
// with header
|
|||
opt := &cos.BucketPutACLOptions{ |
|||
Header: &cos.ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
_, err := c.Bucket.PutACL(context.Background(), opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
// with body
|
|||
opt = &cos.BucketPutACLOptions{ |
|||
Body: &cos.ACLXml{ |
|||
Owner: &cos.Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
AccessControlList: []cos.ACLGrant{ |
|||
{ |
|||
Grantee: &cos.ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
|
|||
Permission: "FULL_CONTROL", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
_, err = c.Bucket.PutACL(context.Background(), opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
opt := &cos.BucketPutCORSOptions{ |
|||
Rules: []cos.BucketCORSRule{ |
|||
{ |
|||
AllowedOrigins: []string{"http://www.qq.com"}, |
|||
AllowedMethods: []string{"PUT", "GET"}, |
|||
AllowedHeaders: []string{"x-cos-meta-test", "x-cos-xx"}, |
|||
MaxAgeSeconds: 500, |
|||
ExposeHeaders: []string{"x-cos-meta-test1"}, |
|||
}, |
|||
{ |
|||
ID: "1234", |
|||
AllowedOrigins: []string{"http://www.baidu.com", "twitter.com"}, |
|||
AllowedMethods: []string{"PUT", "GET"}, |
|||
MaxAgeSeconds: 500, |
|||
}, |
|||
}, |
|||
} |
|||
_, err := c.Bucket.PutCORS(context.Background(), opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://testhuanan-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
lc := &cos.BucketPutLifecycleOptions{ |
|||
Rules: []cos.BucketLifecycleRule{ |
|||
{ |
|||
ID: "1234", |
|||
Filter: &cos.BucketLifecycleFilter{Prefix: "test"}, |
|||
Status: "Enabled", |
|||
Transition: &cos.BucketLifecycleTransition{ |
|||
Days: 10, |
|||
StorageClass: "Standard", |
|||
}, |
|||
}, |
|||
{ |
|||
ID: "123422", |
|||
// If used for all objecs set Prefix:""
|
|||
Filter: &cos.BucketLifecycleFilter{Prefix: "gg"}, |
|||
Status: "Disabled", |
|||
Expiration: &cos.BucketLifecycleExpiration{ |
|||
Days: 10, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
_, err := c.Bucket.PutLifecycle(context.Background(), lc) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
"time" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
startTime := time.Now() |
|||
|
|||
tg := &cos.BucketPutTaggingOptions{ |
|||
TagSet: []cos.BucketTaggingTag{ |
|||
{ |
|||
Key: "test_k2", |
|||
Value: "test_v2", |
|||
}, |
|||
{ |
|||
Key: "test_k3", |
|||
Value: "test_v3", |
|||
}, |
|||
{ |
|||
Key: startTime.Format("02_Jan_06_15_04_MST"), |
|||
Value: "test_time", |
|||
}, |
|||
}, |
|||
} |
|||
_, err := c.Bucket.PutTagging(context.Background(), tg) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
"time" |
|||
"net/http" |
|||
|
|||
"fmt" |
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("http://tencentyun02-1252448703.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
//设置超时时间
|
|||
Timeout: 100 * time.Second, |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_Key"), |
|||
SecretKey: os.Getenv("COS_Secret"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: false, |
|||
RequestBody: false, |
|||
ResponseHeader: false, |
|||
ResponseBody: false, |
|||
}, |
|||
}, |
|||
}) |
|||
f,err:=os.Open("E:/cos-php-sdk.zip") |
|||
if err!=nil {panic(err)} |
|||
opt := &cos.MultiUploadOptions{ |
|||
OptIni: nil, |
|||
PartSize:1, |
|||
} |
|||
v, _, err := c.Object.MultiUpload( |
|||
context.Background(), "test", f, opt, |
|||
) |
|||
if err!=nil {panic(err)} |
|||
fmt.Println(v) |
|||
} |
@ -0,0 +1,43 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: false, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test_multipart.txt" |
|||
v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", v.UploadID) |
|||
|
|||
resp, err := c.Object.AbortMultipartUpload(context.Background(), name, v.UploadID) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", resp.Status) |
|||
} |
@ -0,0 +1,75 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"bytes" |
|||
"context" |
|||
"fmt" |
|||
"math/rand" |
|||
"net/url" |
|||
"os" |
|||
"time" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func genBigData(blockSize int) []byte { |
|||
b := make([]byte, blockSize) |
|||
if _, err := rand.Read(b); err != nil { |
|||
panic(err) |
|||
} |
|||
return b |
|||
} |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{ |
|||
BucketURL: u, |
|||
} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: false, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
startTime := time.Now() |
|||
|
|||
name := fmt.Sprintf("test/test_object_append_%s", startTime.Format(time.RFC3339)) |
|||
data := genBigData(1024 * 1024 * 1) |
|||
length := len(data) |
|||
r := bytes.NewReader(data) |
|||
|
|||
ctx := context.Background() |
|||
|
|||
// 第一次就必须 append
|
|||
resp, err := c.Object.Append(ctx, name, 0, r, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
return |
|||
} |
|||
fmt.Printf("%s\n", resp.Status) |
|||
|
|||
// head
|
|||
if _, err = c.Object.Head(ctx, name, nil); err != nil { |
|||
panic(err) |
|||
return |
|||
} |
|||
|
|||
// 再次 append
|
|||
data = genBigData(1024 * 1024 * 5) |
|||
r = bytes.NewReader(data) |
|||
resp, err = c.Object.Append(context.Background(), name, length, r, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", resp.Status) |
|||
} |
@ -0,0 +1,95 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"math/rand" |
|||
"net/url" |
|||
"os" |
|||
"strings" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult { |
|||
v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%#v\n", v) |
|||
return v |
|||
} |
|||
|
|||
func uploadPart(c *cos.Client, name string, uploadID string, blockSize, n int) string { |
|||
|
|||
b := make([]byte, blockSize) |
|||
if _, err := rand.Read(b); err != nil { |
|||
panic(err) |
|||
} |
|||
s := fmt.Sprintf("%X", b) |
|||
f := strings.NewReader(s) |
|||
|
|||
resp, err := c.Object.UploadPart( |
|||
context.Background(), name, uploadID, n, f, nil, |
|||
) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", resp.Status) |
|||
return resp.Header.Get("Etag") |
|||
} |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: false, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/test_complete_upload.go" |
|||
up := initUpload(c, name) |
|||
uploadID := up.UploadID |
|||
blockSize := 1024 * 1024 * 3 |
|||
|
|||
opt := &cos.CompleteMultipartUploadOptions{} |
|||
for i := 1; i < 5; i++ { |
|||
etag := uploadPart(c, name, uploadID, blockSize, i) |
|||
opt.Parts = append(opt.Parts, cos.Object{ |
|||
PartNumber: i, ETag: etag}, |
|||
) |
|||
} |
|||
|
|||
c = cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
v, resp, err := c.Object.CompleteMultipartUpload( |
|||
context.Background(), name, uploadID, opt, |
|||
) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", resp.Status) |
|||
fmt.Printf("%#v\n", v) |
|||
fmt.Printf("%s\n", v.Location) |
|||
} |
@ -0,0 +1,63 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
"strings" |
|||
|
|||
"net/http" |
|||
|
|||
"fmt" |
|||
"io/ioutil" |
|||
"time" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
source := "test/objectMove1.go" |
|||
expected := "test" |
|||
f := strings.NewReader(expected) |
|||
|
|||
_, err := c.Object.Put(context.Background(), source, f, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
soruceURL := fmt.Sprintf("%s/%s", u.Host, source) |
|||
dest := fmt.Sprintf("test/objectMove_%d.go", time.Now().Nanosecond()) |
|||
//opt := &cos.ObjectCopyOptions{}
|
|||
res, _, err := c.Object.Copy(context.Background(), dest, soruceURL, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%+v\n\n", res) |
|||
|
|||
resp, err := c.Object.Get(context.Background(), dest, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
bs, _ := ioutil.ReadAll(resp.Body) |
|||
resp.Body.Close() |
|||
result := string(bs) |
|||
if result != expected { |
|||
panic(fmt.Sprintf("%s != %s", result, expected)) |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/objectPut.go" |
|||
|
|||
_, err := c.Object.Delete(context.Background(), name) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,102 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
"time" |
|||
|
|||
"bytes" |
|||
"io" |
|||
|
|||
"math/rand" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func genBigData(blockSize int) []byte { |
|||
b := make([]byte, blockSize) |
|||
if _, err := rand.Read(b); err != nil { |
|||
panic(err) |
|||
} |
|||
return b |
|||
} |
|||
|
|||
func uploadMulti(c *cos.Client) []string { |
|||
names := []string{} |
|||
data := genBigData(1024 * 1024 * 1) |
|||
ctx := context.Background() |
|||
var r io.Reader |
|||
var name string |
|||
n := 3 |
|||
|
|||
for n > 0 { |
|||
name = fmt.Sprintf("test/test_multi_delete_%s", time.Now().Format(time.RFC3339)) |
|||
r = bytes.NewReader(data) |
|||
|
|||
c.Object.Put(ctx, name, r, nil) |
|||
names = append(names, name) |
|||
n-- |
|||
} |
|||
return names |
|||
} |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: false, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
ctx := context.Background() |
|||
|
|||
names := uploadMulti(c) |
|||
names = append(names, []string{"a", "b", "c", "a+bc/xx&?+# "}...) |
|||
obs := []cos.Object{} |
|||
for _, v := range names { |
|||
obs = append(obs, cos.Object{Key: v}) |
|||
} |
|||
//sha1 := ""
|
|||
opt := &cos.ObjectDeleteMultiOptions{ |
|||
Objects: obs, |
|||
//XCosSha1: sha1,
|
|||
//Quiet: true,
|
|||
} |
|||
|
|||
c = cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
v, _, err := c.Object.DeleteMulti(ctx, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
for _, x := range v.DeletedObjects { |
|||
fmt.Printf("deleted %s\n", x.Key) |
|||
} |
|||
for _, x := range v.Errors { |
|||
fmt.Printf("error %s, %s, %s\n", x.Key, x.Code, x.Message) |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"io/ioutil" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/hello.txt" |
|||
resp, err := c.Object.Get(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
bs, _ := ioutil.ReadAll(resp.Body) |
|||
resp.Body.Close() |
|||
fmt.Printf("%s\n", string(bs)) |
|||
|
|||
// range
|
|||
opt := &cos.ObjectGetOptions{ |
|||
ResponseContentType: "text/html", |
|||
Range: "bytes=0-3", |
|||
} |
|||
resp, err = c.Object.Get(context.Background(), name, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
bs, _ = ioutil.ReadAll(resp.Body) |
|||
resp.Body.Close() |
|||
fmt.Printf("%s\n", string(bs)) |
|||
} |
@ -0,0 +1,40 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/hello.txt" |
|||
v, _, err := c.Object.GetACL(context.Background(), name) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
for _, a := range v.AccessControlList { |
|||
fmt.Printf("%s, %s, %s\n", a.Grantee.Type, a.Grantee.ID, a.Permission) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,45 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"strings" |
|||
|
|||
"io/ioutil" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
) |
|||
|
|||
func upload(c *cos.Client, name string) { |
|||
f := strings.NewReader("test") |
|||
f = strings.NewReader("test xxx") |
|||
opt := &cos.ObjectPutOptions{ |
|||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ |
|||
ContentType: "text/html", |
|||
}, |
|||
ACLHeaderOptions: &cos.ACLHeaderOptions{ |
|||
XCosACL: "public-read", |
|||
}, |
|||
} |
|||
c.Object.Put(context.Background(), name, f, opt) |
|||
return |
|||
} |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, nil) |
|||
|
|||
name := "test/anonymous_get.go" |
|||
upload(c, name) |
|||
|
|||
resp, err := c.Object.Get(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
return |
|||
} |
|||
bs, _ := ioutil.ReadAll(resp.Body) |
|||
defer resp.Body.Close() |
|||
fmt.Printf("%s\n", string(bs)) |
|||
} |
@ -0,0 +1,35 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/hello.txt" |
|||
_, err := c.Object.Head(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/url" |
|||
"os" |
|||
"time" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test_multipart" + time.Now().Format(time.RFC3339) |
|||
v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", v.UploadID) |
|||
} |
@ -0,0 +1,81 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"math/rand" |
|||
"net/url" |
|||
"os" |
|||
"strings" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult { |
|||
v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%#v\n", v) |
|||
return v |
|||
} |
|||
|
|||
func uploadPart(c *cos.Client, name string, uploadID string, blockSize, n int) string { |
|||
|
|||
b := make([]byte, blockSize) |
|||
if _, err := rand.Read(b); err != nil { |
|||
panic(err) |
|||
} |
|||
s := fmt.Sprintf("%X", b) |
|||
f := strings.NewReader(s) |
|||
|
|||
resp, err := c.Object.UploadPart( |
|||
context.Background(), name, uploadID, n, f, nil, |
|||
) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%s\n", resp.Status) |
|||
return resp.Header.Get("Etag") |
|||
} |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: false, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/test_list_parts.go" |
|||
up := initUpload(c, name) |
|||
uploadID := up.UploadID |
|||
ctx := context.Background() |
|||
blockSize := 1024 * 1024 * 3 |
|||
|
|||
for i := 1; i < 5; i++ { |
|||
uploadPart(c, name, uploadID, blockSize, i) |
|||
} |
|||
|
|||
v, _, err := c.Object.ListParts(ctx, name, uploadID) |
|||
if err != nil { |
|||
panic(err) |
|||
return |
|||
} |
|||
for _, p := range v.Parts { |
|||
fmt.Printf("%d, %s, %d\n", p.PartNumber, p.ETag, p.Size) |
|||
} |
|||
fmt.Printf("%s\n", v.Initiator.ID) |
|||
fmt.Printf("%s\n", v.Owner.ID) |
|||
} |
@ -0,0 +1,39 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/hello.txt" |
|||
opt := &cos.ObjectOptionsOptions{ |
|||
Origin: "http://www.qq.com", |
|||
AccessControlRequestMethod: "PUT", |
|||
} |
|||
_, err := c.Object.Options(context.Background(), name, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
"strings" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/objectPut.go" |
|||
f := strings.NewReader("test") |
|||
|
|||
_, err := c.Object.Put(context.Background(), name, f, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
name = "test/put_option.go" |
|||
f = strings.NewReader("test xxx") |
|||
opt := &cos.ObjectPutOptions{ |
|||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ |
|||
ContentType: "text/html", |
|||
}, |
|||
ACLHeaderOptions: &cos.ACLHeaderOptions{ |
|||
//XCosACL: "public-read",
|
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
_, err = c.Object.Put(context.Background(), name, f, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
opt := &cos.ObjectPutACLOptions{ |
|||
Header: &cos.ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
name := "test/hello.txt" |
|||
_, err := c.Object.PutACL(context.Background(), name, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
// with body
|
|||
opt = &cos.ObjectPutACLOptions{ |
|||
Body: &cos.ACLXml{ |
|||
Owner: &cos.Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
AccessControlList: []cos.ACLGrant{ |
|||
{ |
|||
Grantee: &cos.ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
|
|||
Permission: "FULL_CONTROL", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
_, err = c.Object.PutACL(context.Background(), name, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"net/url" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"fmt" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_Key"), |
|||
SecretKey: os.Getenv("COS_Secret"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: false, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/uploadFile.go" |
|||
f, err := os.Open(os.Args[0]) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
s, err := f.Stat() |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Println(s.Size()) |
|||
opt := &cos.ObjectPutOptions{ |
|||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ |
|||
ContentLength: int(s.Size()), |
|||
}, |
|||
} |
|||
//opt.ContentLength = int(s.Size())
|
|||
|
|||
_, err = c.Object.Put(context.Background(), name, f, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"os" |
|||
|
|||
"net/url" |
|||
"strings" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func initUpload(c *cos.Client, name string) *cos.InitiateMultipartUploadResult { |
|||
v, _, err := c.Object.InitiateMultipartUpload(context.Background(), name, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
fmt.Printf("%#v\n", v) |
|||
return v |
|||
} |
|||
|
|||
func main() { |
|||
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/test_multi_upload.go" |
|||
up := initUpload(c, name) |
|||
uploadID := up.UploadID |
|||
|
|||
f := strings.NewReader("test heoo") |
|||
_, err := c.Object.UploadPart( |
|||
context.Background(), name, uploadID, 1, f, nil, |
|||
) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"os" |
|||
|
|||
"net/http" |
|||
|
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
) |
|||
|
|||
func main() { |
|||
c := cos.NewClient(nil, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: os.Getenv("COS_SECRETID"), |
|||
SecretKey: os.Getenv("COS_SECRETKEY"), |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
s, _, err := c.Service.Get(context.Background()) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
for _, b := range s.Buckets { |
|||
fmt.Printf("%#v\n", b) |
|||
} |
|||
} |
@ -0,0 +1,101 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/json" |
|||
"fmt" |
|||
"github.com/QcloudApi/qcloud_sign_golang" |
|||
"github.com/tencentyun/cos-go-sdk-v5" |
|||
"github.com/tencentyun/cos-go-sdk-v5/debug" |
|||
"net/http" |
|||
"net/url" |
|||
"strings" |
|||
) |
|||
|
|||
// Use Qcloud api github.com/QcloudApi/qcloud_sign_golang
|
|||
// doc https://cloud.tencent.com/document/product/436/14048
|
|||
type Credent struct { |
|||
SessionToken string `json:"sessionToken"` |
|||
TmpSecretID string `json:"tmpSecretId"` |
|||
TmpSecretKey string `json:"tmpSecretKey"` |
|||
} |
|||
|
|||
// Data data in sts response body
|
|||
type Data struct { |
|||
Credentials Credent `json:"credentials"` |
|||
} |
|||
|
|||
// Response sts response body
|
|||
// In qcloud_sign_golang this response only return ak, sk and token
|
|||
type Response struct { |
|||
Dat Data `json:"data"` |
|||
} |
|||
|
|||
func main() { |
|||
// 替换实际的 SecretId 和 SecretKey
|
|||
secretID := "ak" |
|||
secretKey := "sk" |
|||
|
|||
// 配置
|
|||
config := map[string]interface{}{"secretId": secretID, "secretKey": secretKey, "debug": false} |
|||
|
|||
// 请求参数
|
|||
params := map[string]interface{}{"Region": "gz", "Action": "GetFederationToken", "name": "alantong", "policy": "{\"statement\": [{\"action\": [\"name/cos:GetObject\",\"name/cos:PutObject\"],\"effect\": \"allow\",\"resource\":[\"qcs::cos:ap-guangzhou:uid/1253960454:prefix//1253960454/alangz/*\"]}],\"version\": \"2.0\"}"} |
|||
|
|||
// 发送请求
|
|||
retData, err := QcloudApi.SendRequest("sts", params, config) |
|||
if err != nil { |
|||
fmt.Print("Error.", err) |
|||
return |
|||
} |
|||
r := &Response{} |
|||
err = json.Unmarshal([]byte(retData), r) |
|||
if err != nil { |
|||
fmt.Println(err) |
|||
return |
|||
} |
|||
//获取临时ak、sk、token
|
|||
tAk := r.Dat.Credentials.TmpSecretID |
|||
tSk := r.Dat.Credentials.TmpSecretKey |
|||
token := r.Dat.Credentials.SessionToken |
|||
|
|||
u, _ := url.Parse("https://alangz-1253960454.cos.ap-guangzhou.myqcloud.com") |
|||
b := &cos.BaseURL{BucketURL: u} |
|||
c := cos.NewClient(b, &http.Client{ |
|||
Transport: &cos.AuthorizationTransport{ |
|||
SecretID: tAk, |
|||
SecretKey: tSk, |
|||
SessionToken: token, |
|||
Transport: &debug.DebugRequestTransport{ |
|||
RequestHeader: true, |
|||
RequestBody: true, |
|||
ResponseHeader: true, |
|||
ResponseBody: true, |
|||
}, |
|||
}, |
|||
}) |
|||
|
|||
name := "test/objectPut.go" |
|||
f := strings.NewReader("test") |
|||
|
|||
_, err = c.Object.Put(context.Background(), name, f, nil) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
|
|||
name = "test/put_option.go" |
|||
f = strings.NewReader("test xxx") |
|||
opt := &cos.ObjectPutOptions{ |
|||
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ |
|||
ContentType: "text/html", |
|||
}, |
|||
ACLHeaderOptions: &cos.ACLHeaderOptions{ |
|||
//XCosACL: "public-read",
|
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
_, err = c.Object.Put(context.Background(), name, f, opt) |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
} |
@ -0,0 +1,57 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
function run() { |
|||
go run "$@" |
|||
|
|||
if [ $? -ne 0 ] |
|||
then |
|||
exit 3 |
|||
fi |
|||
} |
|||
|
|||
echo '###### service ####' |
|||
run ./service/get.go |
|||
|
|||
|
|||
echo '##### bucket ####' |
|||
|
|||
run ./bucket/delete.go |
|||
run ./bucket/put.go |
|||
run ./bucket/putACL.go |
|||
run ./bucket/putCORS.go |
|||
run ./bucket/putLifecycle.go |
|||
run ./bucket/putTagging.go |
|||
run ./bucket/get.go |
|||
run ./bucket/getACL.go |
|||
run ./bucket/getCORS.go |
|||
run ./bucket/getLifecycle.go |
|||
run ./bucket/getTagging.go |
|||
run ./bucket/getLocation.go |
|||
run ./bucket/head.go |
|||
run ./bucket/listMultipartUploads.go |
|||
run ./bucket/delete.go |
|||
run ./bucket/deleteCORS.go |
|||
run ./bucket/deleteLifecycle.go |
|||
run ./bucket/deleteTagging.go |
|||
|
|||
|
|||
echo '##### object ####' |
|||
|
|||
run ./bucket/putCORS.go |
|||
run ./object/put.go |
|||
run ./object/uploadFile.go |
|||
run ./object/putACL.go |
|||
run ./object/append.go |
|||
run ./object/get.go |
|||
run ./object/head.go |
|||
run ./object/getAnonymous.go |
|||
run ./object/getACL.go |
|||
run ./object/listParts.go |
|||
run ./object/options.go |
|||
run ./object/initiateMultipartUpload.go |
|||
run ./object/uploadPart.go |
|||
run ./object/completeMultipartUpload.go |
|||
run ./object/abortMultipartUpload.go |
|||
run ./object/delete.go |
|||
run ./object/deleteMultiple.go |
|||
run ./object/copy.go |
@ -0,0 +1,85 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto/md5" |
|||
"crypto/sha1" |
|||
"fmt" |
|||
"net/http" |
|||
) |
|||
|
|||
// 计算 md5 或 sha1 时的分块大小
|
|||
const calDigestBlockSize = 1024 * 1024 * 10 |
|||
|
|||
func calMD5Digest(msg []byte) []byte { |
|||
// TODO: 分块计算,减少内存消耗
|
|||
m := md5.New() |
|||
m.Write(msg) |
|||
return m.Sum(nil) |
|||
} |
|||
|
|||
func calSHA1Digest(msg []byte) []byte { |
|||
// TODO: 分块计算,减少内存消耗
|
|||
m := sha1.New() |
|||
m.Write(msg) |
|||
return m.Sum(nil) |
|||
} |
|||
|
|||
// cloneRequest returns a clone of the provided *http.Request. The clone is a
|
|||
// shallow copy of the struct and its Header map.
|
|||
func cloneRequest(r *http.Request) *http.Request { |
|||
// shallow copy of the struct
|
|||
r2 := new(http.Request) |
|||
*r2 = *r |
|||
// deep copy of the Header
|
|||
r2.Header = make(http.Header, len(r.Header)) |
|||
for k, s := range r.Header { |
|||
r2.Header[k] = append([]string(nil), s...) |
|||
} |
|||
return r2 |
|||
} |
|||
|
|||
// encodeURIComponent like same function in javascript
|
|||
//
|
|||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
|||
//
|
|||
// http://www.ecma-international.org/ecma-262/6.0/#sec-uri-syntax-and-semantics
|
|||
func encodeURIComponent(s string) string { |
|||
var b bytes.Buffer |
|||
written := 0 |
|||
|
|||
for i, n := 0, len(s); i < n; i++ { |
|||
c := s[i] |
|||
|
|||
switch c { |
|||
case '-', '_', '.', '!', '~', '*', '\'', '(', ')': |
|||
continue |
|||
default: |
|||
// Unreserved according to RFC 3986 sec 2.3
|
|||
if 'a' <= c && c <= 'z' { |
|||
|
|||
continue |
|||
|
|||
} |
|||
if 'A' <= c && c <= 'Z' { |
|||
|
|||
continue |
|||
|
|||
} |
|||
if '0' <= c && c <= '9' { |
|||
|
|||
continue |
|||
} |
|||
} |
|||
|
|||
b.WriteString(s[written:i]) |
|||
fmt.Fprintf(&b, "%%%02x", c) |
|||
written = i + 1 |
|||
} |
|||
|
|||
if written == 0 { |
|||
return s |
|||
} |
|||
b.WriteString(s[written:]) |
|||
return b.String() |
|||
} |
@ -0,0 +1,24 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"fmt" |
|||
"testing" |
|||
) |
|||
|
|||
func Test_calSHA1Digest(t *testing.T) { |
|||
want := "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" |
|||
got := fmt.Sprintf("%x", calSHA1Digest([]byte("test"))) |
|||
if got != want { |
|||
|
|||
t.Errorf("calSHA1Digest request sha1: %+v, want %+v", got, want) |
|||
} |
|||
} |
|||
|
|||
func Test_calMD5Digest(t *testing.T) { |
|||
want := "098f6bcd4621d373cade4e832627b4f6" |
|||
got := fmt.Sprintf("%x", calMD5Digest([]byte("test"))) |
|||
if got != want { |
|||
|
|||
t.Errorf("calMD5Digest request md5: %+v, want %+v", got, want) |
|||
} |
|||
} |
@ -0,0 +1,346 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"io" |
|||
"net/http" |
|||
"strings" |
|||
) |
|||
|
|||
// ObjectService ...
|
|||
//
|
|||
// Object 相关 API
|
|||
type ObjectService service |
|||
|
|||
// ObjectGetOptions ...
|
|||
type ObjectGetOptions struct { |
|||
ResponseContentType string `url:"response-content-type,omitempty" header:"-"` |
|||
ResponseContentLanguage string `url:"response-content-language,omitempty" header:"-"` |
|||
ResponseExpires string `url:"response-expires,omitempty" header:"-"` |
|||
ResponseCacheControl string `url:"response-cache-control,omitempty" header:"-"` |
|||
ResponseContentDisposition string `url:"response-content-disposition,omitempty" header:"-"` |
|||
ResponseContentEncoding string `url:"response-content-encoding,omitempty" header:"-"` |
|||
Range string `url:"-" header:"Range,omitempty"` |
|||
IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"` |
|||
} |
|||
|
|||
// Get Object 请求可以将一个文件(Object)下载至本地。
|
|||
// 该操作需要对目标 Object 具有读权限或目标 Object 对所有人都开放了读权限(公有读)。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7753
|
|||
func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name), |
|||
method: http.MethodGet, |
|||
optQuery: opt, |
|||
optHeader: opt, |
|||
disableCloseBody: true, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// ObjectPutHeaderOptions ...
|
|||
type ObjectPutHeaderOptions struct { |
|||
CacheControl string `header:"Cache-Control,omitempty" url:"-"` |
|||
ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"` |
|||
ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"` |
|||
ContentType string `header:"Content-Type,omitempty" url:"-"` |
|||
ContentLength int `header:"Content-Length,omitempty" url:"-"` |
|||
Expect string `header:"Expect,omitempty" url:"-"` |
|||
Expires string `header:"Expires,omitempty" url:"-"` |
|||
XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"` |
|||
// 自定义的 x-cos-meta-* header
|
|||
XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"` |
|||
XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-"` |
|||
// 可选值: Normal, Appendable
|
|||
//XCosObjectType string `header:"x-cos-object-type,omitempty" url:"-"`
|
|||
} |
|||
|
|||
// ObjectPutOptions ...
|
|||
type ObjectPutOptions struct { |
|||
*ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"` |
|||
*ObjectPutHeaderOptions `header:",omitempty" url:"-" xml:"-"` |
|||
} |
|||
|
|||
// Put Object请求可以将一个文件(Oject)上传至指定Bucket。
|
|||
//
|
|||
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7749
|
|||
func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name), |
|||
method: http.MethodPut, |
|||
body: r, |
|||
optHeader: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// ObjectCopyHeaderOptions ...
|
|||
type ObjectCopyHeaderOptions struct { |
|||
XCosMetadataDirective string `header:"x-cos-metadata-directive,omitempty" url:"-" xml:"-"` |
|||
XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-" xml:"-"` |
|||
XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-" xml:"-"` |
|||
XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-" xml:"-"` |
|||
XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-" xml:"-"` |
|||
XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-" xml:"-"` |
|||
// 自定义的 x-cos-meta-* header
|
|||
XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"` |
|||
XCosCopySource string `header:"x-cos-copy-source" url:"-" xml:"-"` |
|||
} |
|||
|
|||
// ObjectCopyOptions ...
|
|||
type ObjectCopyOptions struct { |
|||
*ObjectCopyHeaderOptions `header:",omitempty" url:"-" xml:"-"` |
|||
*ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"` |
|||
} |
|||
|
|||
// ObjectCopyResult ...
|
|||
type ObjectCopyResult struct { |
|||
XMLName xml.Name `xml:"CopyObjectResult"` |
|||
ETag string `xml:"ETag,omitempty"` |
|||
LastModified string `xml:"LastModified,omitempty"` |
|||
} |
|||
|
|||
// Copy ...
|
|||
// Put Object Copy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
|
|||
// 超过 5G 的文件请使用分块上传 Upload - Copy。在拷贝的过程中,文件元属性和 ACL 可以被修改。
|
|||
//
|
|||
// 用户可以通过该接口实现文件移动,文件重命名,修改文件属性和创建副本。
|
|||
//
|
|||
// 注意:在跨帐号复制的时候,需要先设置被复制文件的权限为公有读,或者对目标帐号赋权,同帐号则不需要。
|
|||
//
|
|||
// https://cloud.tencent.com/document/product/436/10881
|
|||
func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *ObjectCopyOptions) (*ObjectCopyResult, *Response, error) { |
|||
var res ObjectCopyResult |
|||
if opt == nil { |
|||
opt = new(ObjectCopyOptions) |
|||
} |
|||
if opt.ObjectCopyHeaderOptions == nil { |
|||
opt.ObjectCopyHeaderOptions = new(ObjectCopyHeaderOptions) |
|||
} |
|||
opt.XCosCopySource = encodeURIComponent(sourceURL) |
|||
|
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name), |
|||
method: http.MethodPut, |
|||
body: nil, |
|||
optHeader: opt, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// Delete Object请求可以将一个文件(Object)删除。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7743
|
|||
func (s *ObjectService) Delete(ctx context.Context, name string) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name), |
|||
method: http.MethodDelete, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// ObjectHeadOptions ...
|
|||
type ObjectHeadOptions struct { |
|||
IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"` |
|||
} |
|||
|
|||
// Head Object请求可以取回对应Object的元数据,Head的权限与Get的权限一致
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7745
|
|||
func (s *ObjectService) Head(ctx context.Context, name string, opt *ObjectHeadOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name), |
|||
method: http.MethodHead, |
|||
optHeader: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
if (resp.Header["X-Cos-Object-Type"] != nil && resp.Header["X-Cos-Object-Type"][0] == "appendable") { |
|||
resp.Header.Add("x-cos-next-append-position",resp.Header["Content-Length"][0]) |
|||
} |
|||
|
|||
return resp, err |
|||
} |
|||
|
|||
// ObjectOptionsOptions ...
|
|||
type ObjectOptionsOptions struct { |
|||
Origin string `url:"-" header:"Origin"` |
|||
AccessControlRequestMethod string `url:"-" header:"Access-Control-Request-Method"` |
|||
AccessControlRequestHeaders string `url:"-" header:"Access-Control-Request-Headers,omitempty"` |
|||
} |
|||
|
|||
// Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。
|
|||
//
|
|||
// 当CORS配置不存在时,请求返回403 Forbidden。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8288
|
|||
func (s *ObjectService) Options(ctx context.Context, name string, opt *ObjectOptionsOptions) (*Response, error) { |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name), |
|||
method: http.MethodOptions, |
|||
optHeader: opt, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// Append ...
|
|||
//
|
|||
// Append请求可以将一个文件(Object)以分块追加的方式上传至 Bucket 中。使用Append Upload的文件必须事前被设定为Appendable。
|
|||
// 当Appendable的文件被执行Put Object的操作以后,文件被覆盖,属性改变为Normal。
|
|||
//
|
|||
// 文件属性可以在Head Object操作中被查询到,当您发起Head Object请求时,会返回自定义Header『x-cos-object-type』,该Header只有两个枚举值:Normal或者Appendable。
|
|||
//
|
|||
// 追加上传建议文件大小1M - 5G。如果position的值和当前Object的长度不致,COS会返回409错误。
|
|||
// 如果Append一个Normal的Object,COS会返回409 ObjectNotAppendable。
|
|||
//
|
|||
// Appendable的文件不可以被复制,不参与版本管理,不参与生命周期管理,不可跨区域复制。
|
|||
//
|
|||
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7741
|
|||
func (s *ObjectService) Append(ctx context.Context, name string, position int, r io.Reader, opt *ObjectPutOptions) (*Response, error) { |
|||
u := fmt.Sprintf("/%s?append&position=%d", encodeURIComponent(name), position) |
|||
if position != 0{ |
|||
opt = nil |
|||
} |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: u, |
|||
method: http.MethodPost, |
|||
optHeader: opt, |
|||
body: r, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// ObjectDeleteMultiOptions ...
|
|||
type ObjectDeleteMultiOptions struct { |
|||
XMLName xml.Name `xml:"Delete" header:"-"` |
|||
Quiet bool `xml:"Quiet" header:"-"` |
|||
Objects []Object `xml:"Object" header:"-"` |
|||
//XCosSha1 string `xml:"-" header:"x-cos-sha1"`
|
|||
} |
|||
|
|||
// ObjectDeleteMultiResult ...
|
|||
type ObjectDeleteMultiResult struct { |
|||
XMLName xml.Name `xml:"DeleteResult"` |
|||
DeletedObjects []Object `xml:"Deleted,omitempty"` |
|||
Errors []struct { |
|||
Key string |
|||
Code string |
|||
Message string |
|||
} `xml:"Error,omitempty"` |
|||
} |
|||
|
|||
// DeleteMulti ...
|
|||
//
|
|||
// Delete Multiple Object请求实现批量删除文件,最大支持单次删除1000个文件。
|
|||
// 对于返回结果,COS提供Verbose和Quiet两种结果模式。Verbose模式将返回每个Object的删除结果;
|
|||
// Quiet模式只返回报错的Object信息。
|
|||
//
|
|||
// 此请求必须携带x-cos-sha1用来校验Body的完整性。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8289
|
|||
func (s *ObjectService) DeleteMulti(ctx context.Context, opt *ObjectDeleteMultiOptions) (*ObjectDeleteMultiResult, *Response, error) { |
|||
var res ObjectDeleteMultiResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/?delete", |
|||
method: http.MethodPost, |
|||
body: opt, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// Object ...
|
|||
type Object struct { |
|||
Key string `xml:",omitempty"` |
|||
ETag string `xml:",omitempty"` |
|||
Size int `xml:",omitempty"` |
|||
PartNumber int `xml:",omitempty"` |
|||
LastModified string `xml:",omitempty"` |
|||
StorageClass string `xml:",omitempty"` |
|||
Owner *Owner `xml:",omitempty"` |
|||
} |
|||
|
|||
type MultiUploadOptions struct { |
|||
OptIni *InitiateMultipartUploadOptions |
|||
PartSize int |
|||
} |
|||
|
|||
// MultiUpload 为高级upload接口,并发分块上传
|
|||
//
|
|||
// 需要指定分块大小 partSize >= 1 ,单位为MB
|
|||
// 同时请确认分块数量不超过10000
|
|||
//
|
|||
|
|||
func (s *ObjectService) MultiUpload(ctx context.Context, name string, r io.Reader, opt *MultiUploadOptions) (*CompleteMultipartUploadResult, *Response, error) { |
|||
|
|||
optini := opt.OptIni |
|||
res, _, err := s.InitiateMultipartUpload(ctx, name, optini) |
|||
if err != nil{panic(err)} |
|||
uploadID := res.UploadID |
|||
bufSize := opt.PartSize * 1024 *1024 |
|||
buffer := make([]byte,bufSize) |
|||
optcom := &CompleteMultipartUploadOptions{} |
|||
|
|||
PartUpload := func(ch chan *Response, ctx context.Context, name string, uploadId string, partNumber int, data io.Reader, opt *ObjectUploadPartOptions) { |
|||
|
|||
defer func(){ |
|||
if err := recover(); err != nil { |
|||
fmt.Println(err) |
|||
} |
|||
}() |
|||
resp, err := s.UploadPart(context.Background(), name, uploadId, partNumber, data, nil) |
|||
if err!=nil{ |
|||
panic(err) |
|||
} |
|||
ch <- resp |
|||
} |
|||
|
|||
chs := make([]chan *Response, 10000) |
|||
PartNumber := 0 |
|||
for i := 1 ;true; i++ { |
|||
bytesread,err := r.Read(buffer) |
|||
if err != nil { |
|||
if err != io.EOF { |
|||
panic(err) |
|||
} |
|||
PartNumber = i |
|||
break |
|||
} |
|||
chs[i] = make(chan *Response) |
|||
go PartUpload(chs[i], context.Background(), name, uploadID, i, strings.NewReader(string(buffer[:bytesread])), nil) |
|||
} |
|||
|
|||
for i := 1; i < PartNumber; i++ { |
|||
resp := <-chs[i] |
|||
etag := resp.Header.Get("ETag") |
|||
optcom.Parts = append(optcom.Parts, Object{ |
|||
PartNumber: i, ETag: etag}, |
|||
) |
|||
} |
|||
|
|||
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom) |
|||
|
|||
return v, resp, err |
|||
} |
@ -0,0 +1,63 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"net/http" |
|||
) |
|||
|
|||
// ObjectGetACLResult ...
|
|||
type ObjectGetACLResult ACLXml |
|||
|
|||
// GetACL Get Object ACL接口实现使用API读取Object的ACL表,只有所有者有权操作。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7744
|
|||
func (s *ObjectService) GetACL(ctx context.Context, name string) (*ObjectGetACLResult, *Response, error) { |
|||
var res ObjectGetACLResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name) + "?acl", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// ObjectPutACLOptions ...
|
|||
type ObjectPutACLOptions struct { |
|||
Header *ACLHeaderOptions `url:"-" xml:"-"` |
|||
Body *ACLXml `url:"-" header:"-"` |
|||
} |
|||
|
|||
// PutACL 使用API写入Object的ACL表,您可以通过Header:"x-cos-acl", "x-cos-grant-read" ,
|
|||
// "x-cos-grant-write" ,"x-cos-grant-full-control"传入ACL信息,
|
|||
// 也可以通过body以XML格式传入ACL信息,但是只能选择Header和Body其中一种,否则,返回冲突。
|
|||
//
|
|||
// Put Object ACL是一个覆盖操作,传入新的ACL将覆盖原有ACL。只有所有者有权操作。
|
|||
//
|
|||
// "x-cos-acl":枚举值为public-read,private;public-read意味这个Object有公有读私有写的权限,
|
|||
// private意味这个Object有私有读写的权限。
|
|||
//
|
|||
// "x-cos-grant-read":意味被赋予权限的用户拥有该Object的读权限
|
|||
//
|
|||
// "x-cos-grant-write":意味被赋予权限的用户拥有该Object的写权限
|
|||
//
|
|||
// "x-cos-grant-full-control":意味被赋予权限的用户拥有该Object的读写权限
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7748
|
|||
func (s *ObjectService) PutACL(ctx context.Context, name string, opt *ObjectPutACLOptions) (*Response, error) { |
|||
header := opt.Header |
|||
body := opt.Body |
|||
if body != nil { |
|||
header = nil |
|||
} |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name) + "?acl", |
|||
method: http.MethodPut, |
|||
optHeader: header, |
|||
body: body, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
@ -0,0 +1,174 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestObjectService_GetACL(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
vs := values{ |
|||
"acl": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<AccessControlPolicy> |
|||
<Owner> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>qcs::cam::uin/100000760461:uin/100000760461</DisplayName> |
|||
</Owner> |
|||
<AccessControlList> |
|||
<Grant> |
|||
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="RootAccount"> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>qcs::cam::uin/100000760461:uin/100000760461</DisplayName> |
|||
</Grantee> |
|||
<Permission>FULL_CONTROL</Permission> |
|||
</Grant> |
|||
<Grant> |
|||
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="RootAccount"> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>qcs::cam::uin/100000760461:uin/100000760461</DisplayName> |
|||
</Grantee> |
|||
<Permission>READ</Permission> |
|||
</Grant> |
|||
</AccessControlList> |
|||
</AccessControlPolicy>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Object.GetACL(context.Background(), name) |
|||
if err != nil { |
|||
t.Fatalf("Object.GetACL returned error: %v", err) |
|||
} |
|||
|
|||
want := &ObjectGetACLResult{ |
|||
XMLName: xml.Name{Local: "AccessControlPolicy"}, |
|||
Owner: &Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
AccessControlList: []ACLGrant{ |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
Permission: "FULL_CONTROL", |
|||
}, |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
Permission: "READ", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.GetACL returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestObjectService_PutACL_with_header_opt(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &ObjectPutACLOptions{ |
|||
Header: &ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"acl": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
testHeader(t, r, "x-cos-acl", "private") |
|||
|
|||
want := 0 |
|||
v, _ := r.Body.Read([]byte{}) |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Object.PutACL request body: %#v, want %#v", v, want) |
|||
} |
|||
}) |
|||
|
|||
_, err := client.Object.PutACL(context.Background(), name, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.PutACL returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_PutACL_with_body_opt(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &ObjectPutACLOptions{ |
|||
Body: &ACLXml{ |
|||
Owner: &Owner{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
AccessControlList: []ACLGrant{ |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
|
|||
Permission: "FULL_CONTROL", |
|||
}, |
|||
{ |
|||
Grantee: &ACLGrantee{ |
|||
Type: "RootAccount", |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
}, |
|||
Permission: "READ", |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(ACLXml) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"acl": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
testHeader(t, r, "x-cos-acl", "") |
|||
|
|||
want := opt.Body |
|||
want.XMLName = xml.Name{Local: "AccessControlPolicy"} |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Object.PutACL request body: %+v, want %+v", v, want) |
|||
} |
|||
|
|||
}) |
|||
|
|||
_, err := client.Object.PutACL(context.Background(), name, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.PutACL returned error: %v", err) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,178 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"io" |
|||
"net/http" |
|||
) |
|||
|
|||
// InitiateMultipartUploadOptions ...
|
|||
type InitiateMultipartUploadOptions struct { |
|||
*ACLHeaderOptions |
|||
*ObjectPutHeaderOptions |
|||
} |
|||
|
|||
// InitiateMultipartUploadResult ...
|
|||
type InitiateMultipartUploadResult struct { |
|||
XMLName xml.Name `xml:"InitiateMultipartUploadResult"` |
|||
Bucket string |
|||
Key string |
|||
UploadID string `xml:"UploadId"` |
|||
} |
|||
|
|||
// InitiateMultipartUpload ...
|
|||
//
|
|||
// Initiate Multipart Upload请求实现初始化分片上传,成功执行此请求以后会返回Upload ID用于后续的Upload Part请求。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7746
|
|||
func (s *ObjectService) InitiateMultipartUpload(ctx context.Context, name string, opt *InitiateMultipartUploadOptions) (*InitiateMultipartUploadResult, *Response, error) { |
|||
var res InitiateMultipartUploadResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: "/" + encodeURIComponent(name) + "?uploads", |
|||
method: http.MethodPost, |
|||
optHeader: opt, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// ObjectUploadPartOptions ...
|
|||
type ObjectUploadPartOptions struct { |
|||
Expect string `header:"Expect,omitempty" url:"-"` |
|||
XCosContentSHA1 string `header:"x-cos-content-sha1" url:"-"` |
|||
ContentLength int `header:"Content-Length,omitempty" url:"-"` |
|||
} |
|||
|
|||
|
|||
// UploadPart ...
|
|||
//
|
|||
// Upload Part请求实现在初始化以后的分块上传,支持的块的数量为1到10000,块的大小为1 MB 到5 GB。
|
|||
// 在每次请求Upload Part时候,需要携带partNumber和uploadID,partNumber为块的编号,支持乱序上传。
|
|||
//
|
|||
// 当传入uploadID和partNumber都相同的时候,后传入的块将覆盖之前传入的块。当uploadID不存在时会返回404错误,NoSuchUpload.
|
|||
//
|
|||
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ContentLength
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7750
|
|||
func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) { |
|||
u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID) |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: u, |
|||
method: http.MethodPut, |
|||
optHeader: opt, |
|||
body: r, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
|||
|
|||
// ObjectListPartsOptions ...
|
|||
type ObjectListPartsOptions struct { |
|||
EncodingType string `url:"Encoding-type,omitempty"` |
|||
MaxParts int `url:"max-parts,omitempty"` |
|||
PartNumberMarker int `url:"part-number-marker,omitempty"` |
|||
} |
|||
|
|||
// ObjectListPartsResult ...
|
|||
type ObjectListPartsResult struct { |
|||
XMLName xml.Name `xml:"ListPartsResult"` |
|||
Bucket string |
|||
EncodingType string `xml:"Encoding-type,omitempty"` |
|||
Key string |
|||
UploadID string `xml:"UploadId"` |
|||
Initiator *Initiator `xml:"Initiator,omitempty"` |
|||
Owner *Owner `xml:"Owner,omitempty"` |
|||
StorageClass string |
|||
PartNumberMarker int |
|||
NextPartNumberMarker int `xml:"NextPartNumberMarker,omitempty"` |
|||
MaxParts int |
|||
IsTruncated bool |
|||
Parts []Object `xml:"Part,omitempty"` |
|||
} |
|||
|
|||
// ListParts ...
|
|||
//
|
|||
// List Parts用来查询特定分块上传中的已上传的块。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7747
|
|||
func (s *ObjectService) ListParts(ctx context.Context, name, uploadID string) (*ObjectListPartsResult, *Response, error) { |
|||
u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID) |
|||
var res ObjectListPartsResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: u, |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// CompleteMultipartUploadOptions ...
|
|||
type CompleteMultipartUploadOptions struct { |
|||
XMLName xml.Name `xml:"CompleteMultipartUpload"` |
|||
Parts []Object `xml:"Part"` |
|||
} |
|||
|
|||
// CompleteMultipartUploadResult ...
|
|||
type CompleteMultipartUploadResult struct { |
|||
XMLName xml.Name `xml:"CompleteMultipartUploadResult"` |
|||
Location string |
|||
Bucket string |
|||
Key string |
|||
ETag string |
|||
} |
|||
|
|||
// CompleteMultipartUpload ...
|
|||
//
|
|||
// Complete Multipart Upload用来实现完成整个分块上传。当您已经使用Upload Parts上传所有块以后,你可以用该API完成上传。
|
|||
// 在使用该API时,您必须在Body中给出每一个块的PartNumber和ETag,用来校验块的准确性。
|
|||
//
|
|||
// 由于分块上传的合并需要数分钟时间,因而当合并分块开始的时候,COS就立即返回200的状态码,在合并的过程中,
|
|||
// COS会周期性的返回空格信息来保持连接活跃,直到合并完成,COS会在Body中返回合并后块的内容。
|
|||
//
|
|||
// 当上传块小于1 MB的时候,在调用该请求时,会返回400 EntityTooSmall;
|
|||
// 当上传块编号不连续的时候,在调用该请求时,会返回400 InvalidPart;
|
|||
// 当请求Body中的块信息没有按序号从小到大排列的时候,在调用该请求时,会返回400 InvalidPartOrder;
|
|||
// 当UploadId不存在的时候,在调用该请求时,会返回404 NoSuchUpload。
|
|||
//
|
|||
// 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7742
|
|||
func (s *ObjectService) CompleteMultipartUpload(ctx context.Context, name, uploadID string, opt *CompleteMultipartUploadOptions) (*CompleteMultipartUploadResult, *Response, error) { |
|||
u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID) |
|||
var res CompleteMultipartUploadResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: u, |
|||
method: http.MethodPost, |
|||
body: opt, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
|||
|
|||
// AbortMultipartUpload ...
|
|||
//
|
|||
// Abort Multipart Upload用来实现舍弃一个分块上传并删除已上传的块。当您调用Abort Multipart Upload时,
|
|||
// 如果有正在使用这个Upload Parts上传块的请求,则Upload Parts会返回失败。当该UploadID不存在时,会返回404 NoSuchUpload。
|
|||
//
|
|||
// 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/7740
|
|||
func (s *ObjectService) AbortMultipartUpload(ctx context.Context, name, uploadID string) (*Response, error) { |
|||
u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID) |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.BucketURL, |
|||
uri: u, |
|||
method: http.MethodDelete, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return resp, err |
|||
} |
@ -0,0 +1,273 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"bytes" |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestObjectService_AbortMultipartUpload(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
uploadID := "xxxxaabcc" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodDelete) |
|||
vs := values{ |
|||
"uploadId": uploadID, |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
_, err := client.Object.AbortMultipartUpload(context.Background(), |
|||
name, uploadID) |
|||
if err != nil { |
|||
t.Fatalf("Object.AbortMultipartUpload returned error: %v", err) |
|||
} |
|||
} |
|||
|
|||
func TestObjectService_InitiateMultipartUpload(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &InitiateMultipartUploadOptions{ |
|||
ObjectPutHeaderOptions: &ObjectPutHeaderOptions{ |
|||
ContentType: "text/html", |
|||
}, |
|||
ACLHeaderOptions: &ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(BucketPutTaggingOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodPost) |
|||
testHeader(t, r, "x-cos-acl", "private") |
|||
testHeader(t, r, "Content-Type", "text/html") |
|||
vs := values{ |
|||
"uploads": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<InitiateMultipartUploadResult> |
|||
<Bucket>test-1253846586</Bucket> |
|||
<Key>test/hello.txt</Key> |
|||
<UploadId>149795166761115ef06e259b2fceb8ff34bf7dd840883d26a0f90243562dd398efa41718db</UploadId> |
|||
</InitiateMultipartUploadResult>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Object.InitiateMultipartUpload(context.Background(), |
|||
name, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.InitiateMultipartUpload returned error: %v", err) |
|||
} |
|||
|
|||
want := &InitiateMultipartUploadResult{ |
|||
XMLName: xml.Name{Local: "InitiateMultipartUploadResult"}, |
|||
Bucket: "test-1253846586", |
|||
Key: "test/hello.txt", |
|||
UploadID: "149795166761115ef06e259b2fceb8ff34bf7dd840883d26a0f90243562dd398efa41718db", |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.InitiateMultipartUpload returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestObjectService_UploadPart(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &ObjectUploadPartOptions{} |
|||
name := "test/hello.txt" |
|||
uploadID := "xxxxx" |
|||
partNumber := 1 |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodPut) |
|||
vs := values{ |
|||
"uploadId": uploadID, |
|||
"partNumber": "1", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
b, _ := ioutil.ReadAll(r.Body) |
|||
v := string(b) |
|||
want := "hello" |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Object.UploadPart request body: %#v, want %#v", v, want) |
|||
} |
|||
}) |
|||
|
|||
r := bytes.NewReader([]byte("hello")) |
|||
_, err := client.Object.UploadPart(context.Background(), |
|||
name, uploadID, partNumber, r, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.UploadPart returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_ListParts(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
name := "test/hello.txt" |
|||
uploadID := "149795194893578fd83aceef3a88f708f81f00e879fda5ea8a80bf15aba52746d42d512387" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(BucketPutTaggingOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodGet) |
|||
vs := values{ |
|||
"uploadId": uploadID, |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
fmt.Fprint(w, `<ListPartsResult> |
|||
<Bucket>test-1253846586</Bucket> |
|||
<Encoding-type/> |
|||
<Key>test/hello.txt</Key> |
|||
<UploadId>149795194893578fd83aceef3a88f708f81f00e879fda5ea8a80bf15aba52746d42d512387</UploadId> |
|||
<Owner> |
|||
<ID>1253846586</ID> |
|||
<DisplayName>1253846586</DisplayName> |
|||
</Owner> |
|||
<PartNumberMarker>0</PartNumberMarker> |
|||
<Initiator> |
|||
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID> |
|||
<DisplayName>100000760461</DisplayName> |
|||
</Initiator> |
|||
<Part> |
|||
<PartNumber>1</PartNumber> |
|||
<LastModified>2017-06-20T09:45:49.000Z</LastModified> |
|||
<ETag>"fae3dba15f4d9b2d76cbaed5de3a08e3"</ETag> |
|||
<Size>6291456</Size> |
|||
</Part> |
|||
<Part> |
|||
<PartNumber>2</PartNumber> |
|||
<LastModified>2017-06-20T09:45:50.000Z</LastModified> |
|||
<ETag>"c81982550f2f965118d486176d9541d4"</ETag> |
|||
<Size>6391456</Size> |
|||
</Part> |
|||
<StorageClass>Standard</StorageClass> |
|||
<MaxParts>1000</MaxParts> |
|||
<IsTruncated>false</IsTruncated> |
|||
</ListPartsResult>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Object.ListParts(context.Background(), |
|||
name, uploadID) |
|||
if err != nil { |
|||
t.Fatalf("Object.ListParts returned error: %v", err) |
|||
} |
|||
|
|||
want := &ObjectListPartsResult{ |
|||
XMLName: xml.Name{Local: "ListPartsResult"}, |
|||
Bucket: "test-1253846586", |
|||
UploadID: uploadID, |
|||
Key: name, |
|||
Owner: &Owner{ |
|||
ID: "1253846586", |
|||
DisplayName: "1253846586", |
|||
}, |
|||
PartNumberMarker: 0, |
|||
Initiator: &Initiator{ |
|||
ID: "qcs::cam::uin/100000760461:uin/100000760461", |
|||
DisplayName: "100000760461", |
|||
}, |
|||
Parts: []Object{ |
|||
{ |
|||
PartNumber: 1, |
|||
LastModified: "2017-06-20T09:45:49.000Z", |
|||
ETag: "\"fae3dba15f4d9b2d76cbaed5de3a08e3\"", |
|||
Size: 6291456, |
|||
}, |
|||
{ |
|||
PartNumber: 2, |
|||
LastModified: "2017-06-20T09:45:50.000Z", |
|||
ETag: "\"c81982550f2f965118d486176d9541d4\"", |
|||
Size: 6391456, |
|||
}, |
|||
}, |
|||
StorageClass: "Standard", |
|||
MaxParts: 1000, |
|||
IsTruncated: false, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.ListParts returned \n%#v, want \n%#v", ref, want) |
|||
} |
|||
} |
|||
|
|||
func TestObjectService_CompleteMultipartUpload(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
uploadID := "149795194893578fd83aceef3a88f708f81f00e879fda5ea8a80bf15aba52746d42d512387" |
|||
|
|||
opt := &CompleteMultipartUploadOptions{ |
|||
Parts: []Object{ |
|||
{ |
|||
PartNumber: 1, |
|||
ETag: "\"fae3dba15f4d9b2d76cbaed5de3a08e3\"", |
|||
}, |
|||
{ |
|||
PartNumber: 2, |
|||
ETag: "\"c81982550f2f965118d486176d9541d4\"", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
v := new(CompleteMultipartUploadOptions) |
|||
xml.NewDecoder(r.Body).Decode(v) |
|||
|
|||
testMethod(t, r, http.MethodPost) |
|||
vs := values{ |
|||
"uploadId": uploadID, |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
want := opt |
|||
want.XMLName = xml.Name{Local: "CompleteMultipartUpload"} |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Object.CompleteMultipartUpload request body: %+v, want %+v", v, want) |
|||
} |
|||
fmt.Fprint(w, `<CompleteMultipartUploadResult> |
|||
<Location>test-1253846586.cos.ap-guangzhou.myqcloud.com/test/hello.txt</Location> |
|||
<Bucket>test</Bucket> |
|||
<Key>test/hello.txt</Key> |
|||
<ETag>"594f98b11c6901c0f0683de1085a6d0e-4"</ETag> |
|||
</CompleteMultipartUploadResult>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Object.CompleteMultipartUpload(context.Background(), |
|||
name, uploadID, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.ListParts returned error: %v", err) |
|||
} |
|||
|
|||
want := &CompleteMultipartUploadResult{ |
|||
XMLName: xml.Name{Local: "CompleteMultipartUploadResult"}, |
|||
Bucket: "test", |
|||
Key: name, |
|||
ETag: "\"594f98b11c6901c0f0683de1085a6d0e-4\"", |
|||
Location: "test-1253846586.cos.ap-guangzhou.myqcloud.com/test/hello.txt", |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.CompleteMultipartUpload returned \n%#v, want \n%#v", ref, want) |
|||
} |
|||
} |
@ -0,0 +1,274 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"bytes" |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestObjectService_Get(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
vs := values{ |
|||
"response-content-type": "text/html", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
testHeader(t, r, "Range", "bytes=0-3") |
|||
fmt.Fprint(w, `hello`) |
|||
}) |
|||
|
|||
opt := &ObjectGetOptions{ |
|||
ResponseContentType: "text/html", |
|||
Range: "bytes=0-3", |
|||
} |
|||
|
|||
resp, err := client.Object.Get(context.Background(), name, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.Get returned error: %v", err) |
|||
} |
|||
|
|||
b, _ := ioutil.ReadAll(resp.Body) |
|||
ref := string(b) |
|||
want := "hello" |
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.Get returned %+v, want %+v", ref, want) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_Put(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &ObjectPutOptions{ |
|||
ObjectPutHeaderOptions: &ObjectPutHeaderOptions{ |
|||
ContentType: "text/html", |
|||
}, |
|||
ACLHeaderOptions: &ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodPut) |
|||
testHeader(t, r, "x-cos-acl", "private") |
|||
testHeader(t, r, "Content-Type", "text/html") |
|||
|
|||
b, _ := ioutil.ReadAll(r.Body) |
|||
v := string(b) |
|||
want := "hello" |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Object.Put request body: %#v, want %#v", v, want) |
|||
} |
|||
}) |
|||
|
|||
r := bytes.NewReader([]byte("hello")) |
|||
_, err := client.Object.Put(context.Background(), name, r, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.Put returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_Delete(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodDelete) |
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
_, err := client.Object.Delete(context.Background(), name) |
|||
if err != nil { |
|||
t.Fatalf("Object.Delete returned error: %v", err) |
|||
} |
|||
} |
|||
|
|||
func TestObjectService_Head(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "HEAD") |
|||
testHeader(t, r, "If-Modified-Since", "Mon, 12 Jun 2017 05:36:19 GMT") |
|||
}) |
|||
|
|||
opt := &ObjectHeadOptions{ |
|||
IfModifiedSince: "Mon, 12 Jun 2017 05:36:19 GMT", |
|||
} |
|||
|
|||
_, err := client.Object.Head(context.Background(), name, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.Head returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_Options(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
name := "test/hello.txt" |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodOptions) |
|||
testHeader(t, r, "Access-Control-Request-Method", "PUT") |
|||
testHeader(t, r, "Origin", "www.qq.com") |
|||
}) |
|||
|
|||
opt := &ObjectOptionsOptions{ |
|||
Origin: "www.qq.com", |
|||
AccessControlRequestMethod: "PUT", |
|||
} |
|||
|
|||
_, err := client.Object.Options(context.Background(), name, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.Options returned error: %v", err) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_Append(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
opt := &ObjectPutOptions{ |
|||
ObjectPutHeaderOptions: &ObjectPutHeaderOptions{ |
|||
ContentType: "text/html", |
|||
}, |
|||
ACLHeaderOptions: &ACLHeaderOptions{ |
|||
XCosACL: "private", |
|||
}, |
|||
} |
|||
name := "test/hello.txt" |
|||
position := 0 |
|||
|
|||
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) { |
|||
vs := values{ |
|||
"append": "", |
|||
"position": "0", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
|
|||
testMethod(t, r, http.MethodPost) |
|||
testHeader(t, r, "x-cos-acl", "private") |
|||
testHeader(t, r, "Content-Type", "text/html") |
|||
|
|||
b, _ := ioutil.ReadAll(r.Body) |
|||
v := string(b) |
|||
want := "hello" |
|||
if !reflect.DeepEqual(v, want) { |
|||
t.Errorf("Object.Append request body: %#v, want %#v", v, want) |
|||
} |
|||
}) |
|||
|
|||
r := bytes.NewReader([]byte("hello")) |
|||
_, err := client.Object.Append(context.Background(), name, position, r, opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.Append returned error: %v", err) |
|||
} |
|||
} |
|||
|
|||
func TestObjectService_DeleteMulti(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodPost) |
|||
vs := values{ |
|||
"delete": "", |
|||
} |
|||
testFormValues(t, r, vs) |
|||
fmt.Fprint(w, `<DeleteResult> |
|||
<Deleted> |
|||
<Key>test1</Key> |
|||
</Deleted> |
|||
<Deleted> |
|||
<Key>test3</Key> |
|||
</Deleted> |
|||
<Deleted> |
|||
<Key>test2</Key> |
|||
</Deleted> |
|||
</DeleteResult>`) |
|||
}) |
|||
|
|||
opt := &ObjectDeleteMultiOptions{ |
|||
Objects: []Object{ |
|||
{ |
|||
Key: "test1", |
|||
}, |
|||
{ |
|||
Key: "test3", |
|||
}, |
|||
{ |
|||
Key: "test2", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
ref, _, err := client.Object.DeleteMulti(context.Background(), opt) |
|||
if err != nil { |
|||
t.Fatalf("Object.DeleteMulti returned error: %v", err) |
|||
} |
|||
|
|||
want := &ObjectDeleteMultiResult{ |
|||
XMLName: xml.Name{Local: "DeleteResult"}, |
|||
DeletedObjects: []Object{ |
|||
{ |
|||
Key: "test1", |
|||
}, |
|||
{ |
|||
Key: "test3", |
|||
}, |
|||
{ |
|||
Key: "test2", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.DeleteMulti returned %+v, want %+v", ref, want) |
|||
} |
|||
|
|||
} |
|||
|
|||
func TestObjectService_Copy(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/test.go.copy", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, http.MethodPut) |
|||
fmt.Fprint(w, `<CopyObjectResult> |
|||
<ETag>"098f6bcd4621d373cade4e832627b4f6"</ETag> |
|||
<LastModified>2017-12-13T14:53:12</LastModified> |
|||
</CopyObjectResult>`) |
|||
}) |
|||
|
|||
sourceURL := "test-1253846586.cos.ap-guangzhou.myqcloud.com/test.source" |
|||
ref, _, err := client.Object.Copy(context.Background(), "test.go.copy", sourceURL, nil) |
|||
if err != nil { |
|||
t.Fatalf("Object.Copy returned error: %v", err) |
|||
} |
|||
|
|||
want := &ObjectCopyResult{ |
|||
XMLName: xml.Name{Local: "CopyObjectResult"}, |
|||
ETag: `"098f6bcd4621d373cade4e832627b4f6"`, |
|||
LastModified: "2017-12-13T14:53:12", |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Object.Copy returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// ServiceService ...
|
|||
//
|
|||
// Service 相关 API
|
|||
type ServiceService service |
|||
|
|||
// ServiceGetResult ...
|
|||
type ServiceGetResult struct { |
|||
XMLName xml.Name `xml:"ListAllMyBucketsResult"` |
|||
Owner *Owner `xml:"Owner"` |
|||
Buckets []Bucket `xml:"Buckets>Bucket,omitempty"` |
|||
} |
|||
|
|||
// Get Service 接口实现获取该用户下所有Bucket列表。
|
|||
//
|
|||
// 该API接口需要使用Authorization签名认证,
|
|||
// 且只能获取签名中AccessID所属账户的Bucket列表。
|
|||
//
|
|||
// https://www.qcloud.com/document/product/436/8291
|
|||
func (s *ServiceService) Get(ctx context.Context) (*ServiceGetResult, *Response, error) { |
|||
var res ServiceGetResult |
|||
sendOpt := sendOptions{ |
|||
baseURL: s.client.BaseURL.ServiceURL, |
|||
uri: "/", |
|||
method: http.MethodGet, |
|||
result: &res, |
|||
} |
|||
resp, err := s.client.send(ctx, &sendOpt) |
|||
return &res, resp, err |
|||
} |
@ -0,0 +1,66 @@ |
|||
package cos |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"net/http" |
|||
"reflect" |
|||
"testing" |
|||
) |
|||
|
|||
func TestServiceService_Get(t *testing.T) { |
|||
setup() |
|||
defer teardown() |
|||
|
|||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|||
testMethod(t, r, "GET") |
|||
fmt.Fprint(w, `<ListAllMyBucketsResult> |
|||
<Owner> |
|||
<ID>xbaccxx</ID> |
|||
<DisplayName>100000760461</DisplayName> |
|||
</Owner> |
|||
<Buckets> |
|||
<Bucket> |
|||
<Name>huadong-1253846586</Name> |
|||
<Location>ap-shanghai</Location> |
|||
<CreationDate>2017-06-16T13:08:28Z</CreationDate> |
|||
</Bucket> |
|||
<Bucket> |
|||
<Name>huanan-1253846586</Name> |
|||
<Location>ap-guangzhou</Location> |
|||
<CreationDate>2017-06-10T09:00:07Z</CreationDate> |
|||
</Bucket> |
|||
</Buckets> |
|||
</ListAllMyBucketsResult>`) |
|||
}) |
|||
|
|||
ref, _, err := client.Service.Get(context.Background()) |
|||
if err != nil { |
|||
t.Fatalf("Service.Get returned error: %v", err) |
|||
} |
|||
|
|||
want := &ServiceGetResult{ |
|||
XMLName: xml.Name{Local: "ListAllMyBucketsResult"}, |
|||
Owner: &Owner{ |
|||
ID: "xbaccxx", |
|||
DisplayName: "100000760461", |
|||
}, |
|||
Buckets: []Bucket{ |
|||
{ |
|||
Name: "huadong-1253846586", |
|||
Region: "ap-shanghai", |
|||
CreationDate: "2017-06-16T13:08:28Z", |
|||
}, |
|||
{ |
|||
Name: "huanan-1253846586", |
|||
Region: "ap-guangzhou", |
|||
CreationDate: "2017-06-10T09:00:07Z", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
if !reflect.DeepEqual(ref, want) { |
|||
t.Errorf("Service.Get returned %+v, want %+v", ref, want) |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue