Browse Source

first to commit project

tags/v0.7.8
alantong 6 years ago
commit
6ad265fc0c
  1. 7
      .bumpversion.cfg
  2. 29
      .gitignore
  3. 27
      .travis.yml
  4. 21
      LICENSE
  5. 20
      Makefile
  6. 96
      README.md
  7. 264
      auth.go
  8. 51
      auth_test.go
  9. 106
      bucket.go
  10. 62
      bucket_acl.go
  11. 172
      bucket_acl_test.go
  12. 77
      bucket_cors.go
  13. 138
      bucket_cors_test.go
  14. 107
      bucket_lifecycle.go
  15. 140
      bucket_lifecycle_test.go
  16. 30
      bucket_location.go
  17. 39
      bucket_location_test.go
  18. 59
      bucket_part.go
  19. 121
      bucket_part_test.go
  20. 75
      bucket_tagging.go
  21. 115
      bucket_tagging_test.go
  22. 152
      bucket_test.go
  23. 352
      cos.go
  24. 156
      cos_test.go
  25. 70
      debug/http.go
  26. 78
      debug/http_test.go
  27. 29
      doc.go
  28. 49
      error.go
  29. 56
      error_test.go
  30. 8
      example/README.md
  31. 36
      example/bucket/delete.go
  32. 36
      example/bucket/deleteCORS.go
  33. 35
      example/bucket/deleteLifecycle.go
  34. 36
      example/bucket/deleteTagging.go
  35. 46
      example/bucket/get.go
  36. 40
      example/bucket/getACL.go
  37. 41
      example/bucket/getCORS.go
  38. 40
      example/bucket/getLifecycle.go
  39. 38
      example/bucket/getLocation.go
  40. 40
      example/bucket/getTagging.go
  41. 38
      example/bucket/head.go
  42. 43
      example/bucket/listMultipartUploads.go
  43. 39
      example/bucket/put.go
  44. 65
      example/bucket/putACL.go
  45. 53
      example/bucket/putCORS.go
  46. 58
      example/bucket/putLifecycle.go
  47. 54
      example/bucket/putTagging.go
  48. 43
      example/object/MutiUpload.go
  49. 43
      example/object/abortMultipartUpload.go
  50. 75
      example/object/append.go
  51. 95
      example/object/completeMultipartUpload.go
  52. 63
      example/object/copy.go
  53. 36
      example/object/delete.go
  54. 102
      example/object/deleteMultiple.go
  55. 54
      example/object/get.go
  56. 40
      example/object/getACL.go
  57. 45
      example/object/getAnonymous.go
  58. 35
      example/object/head.go
  59. 38
      example/object/initiateMultipartUpload.go
  60. 81
      example/object/listParts.go
  61. 39
      example/object/options.go
  62. 54
      example/object/put.go
  63. 64
      example/object/putACL.go
  64. 53
      example/object/uploadFile.go
  65. 53
      example/object/uploadPart.go
  66. 36
      example/service/get.go
  67. 101
      example/sts/sts.go
  68. 57
      example/test.sh
  69. 85
      helper.go
  70. 24
      helper_test.go
  71. 346
      object.go
  72. 63
      object_acl.go
  73. 174
      object_acl_test.go
  74. 178
      object_part.go
  75. 273
      object_part_test.go
  76. 274
      object_test.go
  77. 37
      service.go
  78. 66
      service_test.go

7
.bumpversion.cfg

@ -0,0 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.7.0
[bumpversion:file:cos.go]

29
.gitignore

@ -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

27
.travis.yml

@ -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

21
LICENSE

@ -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.

20
Makefile

@ -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

96
README.md

@ -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))

264
auth.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
}

51
auth_test.go

@ -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)
}

106
bucket.go

@ -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"`
}

62
bucket_acl.go

@ -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
}

172
bucket_acl_test.go

@ -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)
}
}

77
bucket_cors.go

@ -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
}

138
bucket_cors_test.go

@ -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)
}
}

107
bucket_lifecycle.go

@ -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
}

140
bucket_lifecycle_test.go

@ -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)
}
}

30
bucket_location.go

@ -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
}

39
bucket_location_test.go

@ -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)
}
}

59
bucket_part.go

@ -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
}

121
bucket_part_test.go

@ -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)
}
}

75
bucket_tagging.go

@ -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
}

115
bucket_tagging_test.go

@ -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)
}
}

152
bucket_test.go

@ -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>&quot;&quot;</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>&quot;5b7236085f08b3818bfa40b03c946dcc&quot;</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)
}
}

352
cos.go

@ -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"`
}

156
cos_test.go

@ -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)
}
}

70
debug/http.go

@ -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
}

78
debug/http_test.go

@ -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)
}
}

29
doc.go

@ -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

49
error.go

@ -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
}

56
error_test.go

@ -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)
}
}

8
example/README.md

@ -0,0 +1,8 @@
```
export COS_SECRETID=xx
export COS_SECRETKEY=xxx
go run xxx.go
```

36
example/bucket/delete.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)
}
}

36
example/bucket/deleteCORS.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://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)
}
}

35
example/bucket/deleteLifecycle.go

@ -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)
}
}

36
example/bucket/deleteTagging.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://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)
}
}

46
example/bucket/get.go

@ -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)
}
}

40
example/bucket/getACL.go

@ -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)
}
}

41
example/bucket/getCORS.go

@ -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)
}
}

40
example/bucket/getLifecycle.go

@ -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)
}
}

38
example/bucket/getLocation.go

@ -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)
}

40
example/bucket/getTagging.go

@ -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)
}
}

38
example/bucket/head.go

@ -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)
}

43
example/bucket/listMultipartUploads.go

@ -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)
}
}

39
example/bucket/put.go

@ -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)
}
}

65
example/bucket/putACL.go

@ -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)
}
}

53
example/bucket/putCORS.go

@ -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)
}
}

58
example/bucket/putLifecycle.go

@ -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)
}
}

54
example/bucket/putTagging.go

@ -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)
}
}

43
example/object/MutiUpload.go

@ -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)
}

43
example/object/abortMultipartUpload.go

@ -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)
}

75
example/object/append.go

@ -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)
}

95
example/object/completeMultipartUpload.go

@ -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)
}

63
example/object/copy.go

@ -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))
}
}

36
example/object/delete.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://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)
}
}

102
example/object/deleteMultiple.go

@ -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)
}
}

54
example/object/get.go

@ -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))
}

40
example/object/getACL.go

@ -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)
}
}

45
example/object/getAnonymous.go

@ -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))
}

35
example/object/head.go

@ -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)
}
}

38
example/object/initiateMultipartUpload.go

@ -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)
}

81
example/object/listParts.go

@ -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)
}

39
example/object/options.go

@ -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)
}
}

54
example/object/put.go

@ -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)
}
}

64
example/object/putACL.go

@ -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)
}
}

53
example/object/uploadFile.go

@ -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)
}
}

53
example/object/uploadPart.go

@ -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)
}
}

36
example/service/get.go

@ -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)
}
}

101
example/sts/sts.go

@ -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)
}
}

57
example/test.sh

@ -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

85
helper.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()
}

24
helper_test.go

@ -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)
}
}

346
object.go

@ -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
}

63
object_acl.go

@ -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
}

174
object_acl_test.go

@ -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)
}
}

178
object_part.go

@ -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
}

273
object_part_test.go

@ -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>&quot;fae3dba15f4d9b2d76cbaed5de3a08e3&quot;</ETag>
<Size>6291456</Size>
</Part>
<Part>
<PartNumber>2</PartNumber>
<LastModified>2017-06-20T09:45:50.000Z</LastModified>
<ETag>&quot;c81982550f2f965118d486176d9541d4&quot;</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>&quot;594f98b11c6901c0f0683de1085a6d0e-4&quot;</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)
}
}

274
object_test.go

@ -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)
}
}

37
service.go

@ -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
}

66
service_test.go

@ -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)
}
}
Loading…
Cancel
Save