first to commit project
This commit is contained in:
7
.bumpversion.cfg
Normal file
7
.bumpversion.cfg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[bumpversion]
|
||||||
|
commit = True
|
||||||
|
tag = True
|
||||||
|
current_version = 0.7.0
|
||||||
|
|
||||||
|
[bumpversion:file:cos.go]
|
||||||
|
|
||||||
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -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
Normal file
27
.travis.yml
Normal file
@@ -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
Normal file
21
LICENSE
Normal file
@@ -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
Normal file
20
Makefile
Normal file
@@ -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
Normal file
96
README.md
Normal file
@@ -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
Normal file
264
auth.go
Normal file
@@ -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
Normal file
51
auth_test.go
Normal file
@@ -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
Normal file
106
bucket.go
Normal file
@@ -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
Normal file
62
bucket_acl.go
Normal file
@@ -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
Normal file
172
bucket_acl_test.go
Normal file
@@ -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
Normal file
77
bucket_cors.go
Normal file
@@ -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
Normal file
138
bucket_cors_test.go
Normal file
@@ -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
Normal file
107
bucket_lifecycle.go
Normal file
@@ -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
Normal file
140
bucket_lifecycle_test.go
Normal file
@@ -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
Normal file
30
bucket_location.go
Normal file
@@ -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
Normal file
39
bucket_location_test.go
Normal file
@@ -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
Normal file
59
bucket_part.go
Normal file
@@ -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
Normal file
121
bucket_part_test.go
Normal file
@@ -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
Normal file
75
bucket_tagging.go
Normal file
@@ -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
Normal file
115
bucket_tagging_test.go
Normal file
@@ -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
Normal file
152
bucket_test.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package cos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketService_Get(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
opt := &BucketGetOptions{
|
||||||
|
Prefix: "test",
|
||||||
|
MaxKeys: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodGet)
|
||||||
|
vs := values{
|
||||||
|
"prefix": "test",
|
||||||
|
"max-keys": "2",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
fmt.Fprint(w, `<?xml version='1.0' encoding='utf-8' ?>
|
||||||
|
<ListBucketResult>
|
||||||
|
<Name>test-1253846586</Name>
|
||||||
|
<Prefix>test</Prefix>
|
||||||
|
<Marker/>
|
||||||
|
<MaxKeys>2</MaxKeys>
|
||||||
|
<IsTruncated>true</IsTruncated>
|
||||||
|
<NextMarker>test/delete.txt</NextMarker>
|
||||||
|
<Contents>
|
||||||
|
<Key>test/</Key>
|
||||||
|
<LastModified>2017-06-09T16:32:25.000Z</LastModified>
|
||||||
|
<ETag>""</ETag>
|
||||||
|
<Size>0</Size>
|
||||||
|
<Owner>
|
||||||
|
<ID>1253846586</ID>
|
||||||
|
</Owner>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
<Contents>
|
||||||
|
<Key>test/anonymous_get.go</Key>
|
||||||
|
<LastModified>2017-06-17T15:09:26.000Z</LastModified>
|
||||||
|
<ETag>"5b7236085f08b3818bfa40b03c946dcc"</ETag>
|
||||||
|
<Size>8</Size>
|
||||||
|
<Owner>
|
||||||
|
<ID>1253846586</ID>
|
||||||
|
</Owner>
|
||||||
|
<StorageClass>STANDARD</StorageClass>
|
||||||
|
</Contents>
|
||||||
|
</ListBucketResult>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
ref, _, err := client.Bucket.Get(context.Background(), opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.Get returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &BucketGetResult{
|
||||||
|
XMLName: xml.Name{Local: "ListBucketResult"},
|
||||||
|
Name: "test-1253846586",
|
||||||
|
Prefix: "test",
|
||||||
|
MaxKeys: 2,
|
||||||
|
IsTruncated: true,
|
||||||
|
NextMarker: "test/delete.txt",
|
||||||
|
Contents: []Object{
|
||||||
|
{
|
||||||
|
Key: "test/",
|
||||||
|
LastModified: "2017-06-09T16:32:25.000Z",
|
||||||
|
ETag: "\"\"",
|
||||||
|
Size: 0,
|
||||||
|
Owner: &Owner{
|
||||||
|
ID: "1253846586",
|
||||||
|
},
|
||||||
|
StorageClass: "STANDARD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "test/anonymous_get.go",
|
||||||
|
LastModified: "2017-06-17T15:09:26.000Z",
|
||||||
|
ETag: "\"5b7236085f08b3818bfa40b03c946dcc\"",
|
||||||
|
Size: 8,
|
||||||
|
Owner: &Owner{
|
||||||
|
ID: "1253846586",
|
||||||
|
},
|
||||||
|
StorageClass: "STANDARD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ref, want) {
|
||||||
|
t.Errorf("Bucket.Get returned %+v, want %+v", ref, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketService_Put(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
opt := &BucketPutOptions{
|
||||||
|
XCosACL: "public-read",
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
v := new(BucketPutTaggingOptions)
|
||||||
|
xml.NewDecoder(r.Body).Decode(v)
|
||||||
|
|
||||||
|
testMethod(t, r, "PUT")
|
||||||
|
testHeader(t, r, "x-cos-acl", "public-read")
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.Bucket.Put(context.Background(), opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.Put returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketService_Delete(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodDelete)
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.Bucket.Delete(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.Delete returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketService_Head(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodHead)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.Bucket.Head(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Bucket.Head returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
352
cos.go
Normal file
352
cos.go
Normal file
@@ -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
Normal file
156
cos_test.go
Normal file
@@ -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
Normal file
70
debug/http.go
Normal file
@@ -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
Normal file
78
debug/http_test.go
Normal file
@@ -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
Normal file
29
doc.go
Normal file
@@ -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
Normal file
49
error.go
Normal file
@@ -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
Normal file
56
error_test.go
Normal file
@@ -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
Normal file
8
example/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
export COS_SECRETID=xx
|
||||||
|
export COS_SECRETKEY=xxx
|
||||||
|
|
||||||
|
go run xxx.go
|
||||||
|
```
|
||||||
36
example/bucket/delete.go
Normal file
36
example/bucket/delete.go
Normal file
@@ -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
Normal file
36
example/bucket/deleteCORS.go
Normal file
@@ -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
Normal file
35
example/bucket/deleteLifecycle.go
Normal file
@@ -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
Normal file
36
example/bucket/deleteTagging.go
Normal file
@@ -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
Normal file
46
example/bucket/get.go
Normal file
@@ -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
Normal file
40
example/bucket/getACL.go
Normal file
@@ -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
Normal file
41
example/bucket/getCORS.go
Normal file
@@ -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
Normal file
40
example/bucket/getLifecycle.go
Normal file
@@ -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
Normal file
38
example/bucket/getLocation.go
Normal file
@@ -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
Normal file
40
example/bucket/getTagging.go
Normal file
@@ -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
Normal file
38
example/bucket/head.go
Normal file
@@ -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
Normal file
43
example/bucket/listMultipartUploads.go
Normal file
@@ -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
Normal file
39
example/bucket/put.go
Normal file
@@ -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
Normal file
65
example/bucket/putACL.go
Normal file
@@ -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
Normal file
53
example/bucket/putCORS.go
Normal file
@@ -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
Normal file
58
example/bucket/putLifecycle.go
Normal file
@@ -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
Normal file
54
example/bucket/putTagging.go
Normal file
@@ -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
Normal file
43
example/object/MutiUpload.go
Normal file
@@ -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
Normal file
43
example/object/abortMultipartUpload.go
Normal file
@@ -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
Normal file
75
example/object/append.go
Normal file
@@ -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
Normal file
95
example/object/completeMultipartUpload.go
Normal file
@@ -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
Normal file
63
example/object/copy.go
Normal file
@@ -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
Normal file
36
example/object/delete.go
Normal file
@@ -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
Normal file
102
example/object/deleteMultiple.go
Normal file
@@ -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
Normal file
54
example/object/get.go
Normal file
@@ -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
Normal file
40
example/object/getACL.go
Normal file
@@ -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
Normal file
45
example/object/getAnonymous.go
Normal file
@@ -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
Normal file
35
example/object/head.go
Normal file
@@ -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
Normal file
38
example/object/initiateMultipartUpload.go
Normal file
@@ -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
Normal file
81
example/object/listParts.go
Normal file
@@ -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
Normal file
39
example/object/options.go
Normal file
@@ -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
Normal file
54
example/object/put.go
Normal file
@@ -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
Normal file
64
example/object/putACL.go
Normal file
@@ -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
Normal file
53
example/object/uploadFile.go
Normal file
@@ -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
Normal file
53
example/object/uploadPart.go
Normal file
@@ -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
Normal file
36
example/service/get.go
Normal file
@@ -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
Normal file
101
example/sts/sts.go
Normal file
@@ -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
Normal file
57
example/test.sh
Normal file
@@ -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
Normal file
85
helper.go
Normal file
@@ -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
Normal file
24
helper_test.go
Normal file
@@ -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
Normal file
346
object.go
Normal file
@@ -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
Normal file
63
object_acl.go
Normal file
@@ -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
Normal file
174
object_acl_test.go
Normal file
@@ -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
Normal file
178
object_part.go
Normal file
@@ -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
Normal file
273
object_part_test.go
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
package cos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestObjectService_AbortMultipartUpload(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
name := "test/hello.txt"
|
||||||
|
uploadID := "xxxxaabcc"
|
||||||
|
|
||||||
|
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodDelete)
|
||||||
|
vs := values{
|
||||||
|
"uploadId": uploadID,
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.Object.AbortMultipartUpload(context.Background(),
|
||||||
|
name, uploadID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Object.AbortMultipartUpload returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectService_InitiateMultipartUpload(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
opt := &InitiateMultipartUploadOptions{
|
||||||
|
ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
|
||||||
|
ContentType: "text/html",
|
||||||
|
},
|
||||||
|
ACLHeaderOptions: &ACLHeaderOptions{
|
||||||
|
XCosACL: "private",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
name := "test/hello.txt"
|
||||||
|
|
||||||
|
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
v := new(BucketPutTaggingOptions)
|
||||||
|
xml.NewDecoder(r.Body).Decode(v)
|
||||||
|
|
||||||
|
testMethod(t, r, http.MethodPost)
|
||||||
|
testHeader(t, r, "x-cos-acl", "private")
|
||||||
|
testHeader(t, r, "Content-Type", "text/html")
|
||||||
|
vs := values{
|
||||||
|
"uploads": "",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
fmt.Fprint(w, `<InitiateMultipartUploadResult>
|
||||||
|
<Bucket>test-1253846586</Bucket>
|
||||||
|
<Key>test/hello.txt</Key>
|
||||||
|
<UploadId>149795166761115ef06e259b2fceb8ff34bf7dd840883d26a0f90243562dd398efa41718db</UploadId>
|
||||||
|
</InitiateMultipartUploadResult>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
ref, _, err := client.Object.InitiateMultipartUpload(context.Background(),
|
||||||
|
name, opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Object.InitiateMultipartUpload returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &InitiateMultipartUploadResult{
|
||||||
|
XMLName: xml.Name{Local: "InitiateMultipartUploadResult"},
|
||||||
|
Bucket: "test-1253846586",
|
||||||
|
Key: "test/hello.txt",
|
||||||
|
UploadID: "149795166761115ef06e259b2fceb8ff34bf7dd840883d26a0f90243562dd398efa41718db",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ref, want) {
|
||||||
|
t.Errorf("Object.InitiateMultipartUpload returned %+v, want %+v", ref, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectService_UploadPart(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
opt := &ObjectUploadPartOptions{}
|
||||||
|
name := "test/hello.txt"
|
||||||
|
uploadID := "xxxxx"
|
||||||
|
partNumber := 1
|
||||||
|
|
||||||
|
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodPut)
|
||||||
|
vs := values{
|
||||||
|
"uploadId": uploadID,
|
||||||
|
"partNumber": "1",
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
b, _ := ioutil.ReadAll(r.Body)
|
||||||
|
v := string(b)
|
||||||
|
want := "hello"
|
||||||
|
if !reflect.DeepEqual(v, want) {
|
||||||
|
t.Errorf("Object.UploadPart request body: %#v, want %#v", v, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
r := bytes.NewReader([]byte("hello"))
|
||||||
|
_, err := client.Object.UploadPart(context.Background(),
|
||||||
|
name, uploadID, partNumber, r, opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Object.UploadPart returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectService_ListParts(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
name := "test/hello.txt"
|
||||||
|
uploadID := "149795194893578fd83aceef3a88f708f81f00e879fda5ea8a80bf15aba52746d42d512387"
|
||||||
|
|
||||||
|
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
v := new(BucketPutTaggingOptions)
|
||||||
|
xml.NewDecoder(r.Body).Decode(v)
|
||||||
|
|
||||||
|
testMethod(t, r, http.MethodGet)
|
||||||
|
vs := values{
|
||||||
|
"uploadId": uploadID,
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
fmt.Fprint(w, `<ListPartsResult>
|
||||||
|
<Bucket>test-1253846586</Bucket>
|
||||||
|
<Encoding-type/>
|
||||||
|
<Key>test/hello.txt</Key>
|
||||||
|
<UploadId>149795194893578fd83aceef3a88f708f81f00e879fda5ea8a80bf15aba52746d42d512387</UploadId>
|
||||||
|
<Owner>
|
||||||
|
<ID>1253846586</ID>
|
||||||
|
<DisplayName>1253846586</DisplayName>
|
||||||
|
</Owner>
|
||||||
|
<PartNumberMarker>0</PartNumberMarker>
|
||||||
|
<Initiator>
|
||||||
|
<ID>qcs::cam::uin/100000760461:uin/100000760461</ID>
|
||||||
|
<DisplayName>100000760461</DisplayName>
|
||||||
|
</Initiator>
|
||||||
|
<Part>
|
||||||
|
<PartNumber>1</PartNumber>
|
||||||
|
<LastModified>2017-06-20T09:45:49.000Z</LastModified>
|
||||||
|
<ETag>"fae3dba15f4d9b2d76cbaed5de3a08e3"</ETag>
|
||||||
|
<Size>6291456</Size>
|
||||||
|
</Part>
|
||||||
|
<Part>
|
||||||
|
<PartNumber>2</PartNumber>
|
||||||
|
<LastModified>2017-06-20T09:45:50.000Z</LastModified>
|
||||||
|
<ETag>"c81982550f2f965118d486176d9541d4"</ETag>
|
||||||
|
<Size>6391456</Size>
|
||||||
|
</Part>
|
||||||
|
<StorageClass>Standard</StorageClass>
|
||||||
|
<MaxParts>1000</MaxParts>
|
||||||
|
<IsTruncated>false</IsTruncated>
|
||||||
|
</ListPartsResult>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
ref, _, err := client.Object.ListParts(context.Background(),
|
||||||
|
name, uploadID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Object.ListParts returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &ObjectListPartsResult{
|
||||||
|
XMLName: xml.Name{Local: "ListPartsResult"},
|
||||||
|
Bucket: "test-1253846586",
|
||||||
|
UploadID: uploadID,
|
||||||
|
Key: name,
|
||||||
|
Owner: &Owner{
|
||||||
|
ID: "1253846586",
|
||||||
|
DisplayName: "1253846586",
|
||||||
|
},
|
||||||
|
PartNumberMarker: 0,
|
||||||
|
Initiator: &Initiator{
|
||||||
|
ID: "qcs::cam::uin/100000760461:uin/100000760461",
|
||||||
|
DisplayName: "100000760461",
|
||||||
|
},
|
||||||
|
Parts: []Object{
|
||||||
|
{
|
||||||
|
PartNumber: 1,
|
||||||
|
LastModified: "2017-06-20T09:45:49.000Z",
|
||||||
|
ETag: "\"fae3dba15f4d9b2d76cbaed5de3a08e3\"",
|
||||||
|
Size: 6291456,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PartNumber: 2,
|
||||||
|
LastModified: "2017-06-20T09:45:50.000Z",
|
||||||
|
ETag: "\"c81982550f2f965118d486176d9541d4\"",
|
||||||
|
Size: 6391456,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
StorageClass: "Standard",
|
||||||
|
MaxParts: 1000,
|
||||||
|
IsTruncated: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ref, want) {
|
||||||
|
t.Errorf("Object.ListParts returned \n%#v, want \n%#v", ref, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectService_CompleteMultipartUpload(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
name := "test/hello.txt"
|
||||||
|
uploadID := "149795194893578fd83aceef3a88f708f81f00e879fda5ea8a80bf15aba52746d42d512387"
|
||||||
|
|
||||||
|
opt := &CompleteMultipartUploadOptions{
|
||||||
|
Parts: []Object{
|
||||||
|
{
|
||||||
|
PartNumber: 1,
|
||||||
|
ETag: "\"fae3dba15f4d9b2d76cbaed5de3a08e3\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PartNumber: 2,
|
||||||
|
ETag: "\"c81982550f2f965118d486176d9541d4\"",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
v := new(CompleteMultipartUploadOptions)
|
||||||
|
xml.NewDecoder(r.Body).Decode(v)
|
||||||
|
|
||||||
|
testMethod(t, r, http.MethodPost)
|
||||||
|
vs := values{
|
||||||
|
"uploadId": uploadID,
|
||||||
|
}
|
||||||
|
testFormValues(t, r, vs)
|
||||||
|
|
||||||
|
want := opt
|
||||||
|
want.XMLName = xml.Name{Local: "CompleteMultipartUpload"}
|
||||||
|
if !reflect.DeepEqual(v, want) {
|
||||||
|
t.Errorf("Object.CompleteMultipartUpload request body: %+v, want %+v", v, want)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, `<CompleteMultipartUploadResult>
|
||||||
|
<Location>test-1253846586.cos.ap-guangzhou.myqcloud.com/test/hello.txt</Location>
|
||||||
|
<Bucket>test</Bucket>
|
||||||
|
<Key>test/hello.txt</Key>
|
||||||
|
<ETag>"594f98b11c6901c0f0683de1085a6d0e-4"</ETag>
|
||||||
|
</CompleteMultipartUploadResult>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
ref, _, err := client.Object.CompleteMultipartUpload(context.Background(),
|
||||||
|
name, uploadID, opt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Object.ListParts returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &CompleteMultipartUploadResult{
|
||||||
|
XMLName: xml.Name{Local: "CompleteMultipartUploadResult"},
|
||||||
|
Bucket: "test",
|
||||||
|
Key: name,
|
||||||
|
ETag: "\"594f98b11c6901c0f0683de1085a6d0e-4\"",
|
||||||
|
Location: "test-1253846586.cos.ap-guangzhou.myqcloud.com/test/hello.txt",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ref, want) {
|
||||||
|
t.Errorf("Object.CompleteMultipartUpload returned \n%#v, want \n%#v", ref, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
274
object_test.go
Normal file
274
object_test.go
Normal file
@@ -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
Normal file
37
service.go
Normal file
@@ -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
Normal file
66
service_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user