Browse Source

Merge pull request #117 from agin719/cos-dev-v5

Cos dev v5
master
agin719 4 years ago
committed by GitHub
parent
commit
ca08d6e1a9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ci.go
  2. 2
      cos.go
  3. 39
      costesting/ci_test.go
  4. 75
      example/object/batchGet.go
  5. 73
      example/object/batchUpload.go
  6. 62
      example/object/directory.go
  7. 57
      example/object/download.go
  8. 68
      example/object/moveObject.go
  9. 13
      example/object/uploadFile.go
  10. 2
      example/object/uploadPart.go
  11. 55
      helper.go
  12. 204
      object.go
  13. 3
      object_part.go
  14. 76
      object_test.go

4
ci.go

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"hash/crc64"
"io"
"net/http"
@ -207,6 +208,9 @@ func (s *CIService) GetVideoAuditingJob(ctx context.Context, jobid string) (*Get
// ci put https://cloud.tencent.com/document/product/460/18147
func (s *CIService) Put(ctx context.Context, name string, r io.Reader, uopt *ObjectPutOptions) (*ImageProcessResult, *Response, error) {
if r == nil {
return nil, nil, fmt.Errorf("reader is nil")
}
if err := CheckReaderLen(r); err != nil {
return nil, nil, err
}

2
cos.go

@ -22,7 +22,7 @@ import (
const (
// Version current go sdk version
Version = "0.7.24"
Version = "0.7.25"
userAgent = "cos-go-sdk-v5/" + Version
contentTypeXML = "application/xml"
defaultServiceBaseURL = "http://service.cos.myqcloud.com"

39
costesting/ci_test.go

@ -100,7 +100,7 @@ func (s *CosTestSuite) SetupSuite() {
XCosACL: "public-read",
}
r, err := s.Client.Bucket.Put(context.Background(), opt)
if err != nil && r.StatusCode == 409 {
if err != nil && r != nil && r.StatusCode == 409 {
fmt.Println("BucketAlreadyOwnedByYou")
} else if err != nil {
assert.Nil(s.T(), err, "PutBucket Failed")
@ -142,7 +142,7 @@ func (s *CosTestSuite) TestPutHeadDeleteBucket() {
},
})
r, err := client.Bucket.Put(context.Background(), nil)
if err != nil && r.StatusCode == 409 {
if err != nil && r != nil && r.StatusCode == 409 {
fmt.Println("BucketAlreadyOwnedByYou")
} else if err != nil {
assert.Nil(s.T(), err, "PutBucket Failed")
@ -507,6 +507,37 @@ func (s *CosTestSuite) TestPutGetDeleteObjectByUpload_10MB() {
assert.Nil(s.T(), err, "remove local file Failed")
}
func (s *CosTestSuite) TestPutGetDeleteObjectByUploadAndDownload_10MB() {
// Create tmp file
filePath := "tmpfile" + time.Now().Format(time.RFC3339)
newfile, err := os.Create(filePath)
assert.Nil(s.T(), err, "create tmp file Failed")
defer newfile.Close()
name := "test/objectUpload" + time.Now().Format(time.RFC3339)
b := make([]byte, 1024*1024*10)
_, err = rand.Read(b)
newfile.Write(b)
opt := &cos.MultiUploadOptions{
PartSize: 1,
ThreadPoolSize: 3,
}
_, _, err = s.Client.Object.Upload(context.Background(), name, filePath, opt)
assert.Nil(s.T(), err, "PutObject Failed")
// Over write tmp file
_, err = s.Client.Object.Download(context.Background(), name, filePath, nil)
assert.Nil(s.T(), err, "DownloadObject Failed")
_, err = s.Client.Object.Delete(context.Background(), name)
assert.Nil(s.T(), err, "DeleteObject Failed")
// remove the local tmp file
err = os.Remove(filePath)
assert.Nil(s.T(), err, "remove local file Failed")
}
func (s *CosTestSuite) TestPutGetDeleteObjectSpecialName() {
f := strings.NewReader("test")
name := s.SepFileName + time.Now().Format(time.RFC3339)
@ -606,7 +637,7 @@ func (s *CosTestSuite) TestCopyObject() {
// Notice in intranet the bucket host sometimes has i/o timeout problem
r, err := c.Bucket.Put(context.Background(), opt)
if err != nil && r.StatusCode == 409 {
if err != nil && r != nil && r.StatusCode == 409 {
fmt.Println("BucketAlreadyOwnedByYou")
} else if err != nil {
assert.Nil(s.T(), err, "PutBucket Failed")
@ -971,7 +1002,7 @@ func (s *CosTestSuite) TestMultiCopy() {
// Notice in intranet the bucket host sometimes has i/o timeout problem
r, err := c.Bucket.Put(context.Background(), opt)
if err != nil && r.StatusCode == 409 {
if err != nil && r != nil && r.StatusCode == 409 {
fmt.Println("BucketAlreadyOwnedByYou")
} else if err != nil {
assert.Nil(s.T(), err, "PutBucket Failed")

75
example/object/batchGet.go

@ -0,0 +1,75 @@
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"sync"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func upload(wg *sync.WaitGroup, c *cos.Client, keysCh <-chan string) {
defer wg.Done()
for key := range keysCh {
// 下载文件到当前目录
_, filename := filepath.Split(key)
_, err := c.Object.GetToFile(context.Background(), key, filename, nil)
if err != nil {
log_status(err)
}
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.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,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: false,
},
},
})
keysCh := make(chan string, 2)
keys := []string{"test/test1", "test/test2", "test/test3"}
var wg sync.WaitGroup
threadpool := 2
for i := 0; i < threadpool; i++ {
wg.Add(1)
go upload(&wg, c, keysCh)
}
for _, key := range keys {
keysCh <- key
}
close(keysCh)
wg.Wait()
}

73
example/object/batchUpload.go

@ -0,0 +1,73 @@
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"sync"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func upload(wg *sync.WaitGroup, c *cos.Client, files <-chan string) {
defer wg.Done()
for file := range files {
name := "test/" + file
_, _, err := c.Object.Upload(context.Background(), name, file, nil)
if err != nil {
log_status(err)
}
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.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,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: false,
},
},
})
filesCh := make(chan string, 2)
filePaths := []string{"test1", "test2", "test3"}
var wg sync.WaitGroup
threadpool := 2
for i := 0; i < threadpool; i++ {
wg.Add(1)
go upload(&wg, c, filesCh)
}
for _, filePath := range filePaths {
filesCh <- filePath
}
close(filesCh)
wg.Wait()
}

62
example/object/directory.go

@ -0,0 +1,62 @@
package main
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.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,
// Notice when put a large file and set need the request body, might happend out of memory error.
RequestBody: false,
ResponseHeader: true,
ResponseBody: false,
},
},
})
// 创建文件夹
name := "example/"
_, err := c.Object.Put(context.Background(), name, strings.NewReader(""), nil)
log_status(err)
// 查看文件夹是否存在
_, err = c.Object.Head(context.Background(), name, nil)
log_status(err)
// 删除文件夹
_, err = c.Object.Delete(context.Background(), name)
log_status(err)
}

57
example/object/download.go

@ -0,0 +1,57 @@
package main
import (
"context"
"net/http"
"net/url"
"os"
"fmt"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.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: false,
RequestBody: false,
ResponseHeader: false,
ResponseBody: false,
},
},
})
opt := &cos.MultiDownloadOptions{
ThreadPoolSize: 5,
}
resp, err := c.Object.Download(
context.Background(), "test", "./test1G", opt,
)
log_status(err)
fmt.Printf("done, %v\n", resp.Header)
}

68
example/object/moveObject.go

@ -0,0 +1,68 @@
package main
import (
"context"
"net/url"
"os"
"strings"
"net/http"
"fmt"
"github.com/tencentyun/cos-go-sdk-v5"
"github.com/tencentyun/cos-go-sdk-v5/debug"
)
func log_status(err error) {
if err == nil {
return
}
if cos.IsNotFoundError(err) {
// WARN
fmt.Println("WARN: Resource is not existed")
} else if e, ok := cos.IsCOSError(err); ok {
fmt.Printf("ERROR: Code: %v\n", e.Code)
fmt.Printf("ERROR: Message: %v\n", e.Message)
fmt.Printf("ERROR: Resource: %v\n", e.Resource)
fmt.Printf("ERROR: RequestId: %v\n", e.RequestID)
// ERROR
} else {
fmt.Printf("ERROR: %v\n", err)
// ERROR
}
}
func main() {
u, _ := url.Parse("https://test-1259654469.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/oldfile"
f := strings.NewReader("test")
// 上传文件
_, err := c.Object.Put(context.Background(), source, f, nil)
log_status(err)
// 重命名
dest := "test/newfile"
soruceURL := fmt.Sprintf("%s/%s", u.Host, source)
_, _, err := c.Object.Copy(context.Background(), dest, soruceURL, nil)
log_status(err)
if err == nil {
_, err = c.Object.Delete(context.Background(), source, nil)
log_status(err)
}
}

13
example/object/uploadFile.go

@ -33,12 +33,12 @@ func log_status(err error) {
}
func main() {
u, _ := url.Parse("https://test-1253846586.cos.ap-guangzhou.myqcloud.com")
u, _ := url.Parse("https://test-1259654469.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"),
SecretID: os.Getenv("COS_SECRETID"),
SecretKey: os.Getenv("COS_SECRETKEY"),
Transport: &debug.DebugRequestTransport{
RequestHeader: true,
RequestBody: false,
@ -49,7 +49,7 @@ func main() {
})
name := "test/uploadFile.go"
f, err := os.Open(os.Args[0])
f, err := os.Open("test")
if err != nil {
log_status(err)
return
@ -59,13 +59,12 @@ func main() {
log_status(err)
return
}
fmt.Println(s.Size())
opt := &cos.ObjectPutOptions{
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
ContentLength: int(s.Size()),
ContentLength: s.Size(),
},
}
//opt.ContentLength = int(s.Size())
//opt.ContentLength = s.Size()
_, err = c.Object.Put(context.Background(), name, f, opt)
log_status(err)

2
example/object/uploadPart.go

@ -74,7 +74,7 @@ func main() {
}
opt := &cos.ObjectUploadPartOptions{
Listener: &cos.DefaultProgressListener{},
ContentLength: int(stat.Size()),
ContentLength: stat.Size(),
}
resp, err := c.Object.UploadPart(
context.Background(), name, uploadID, 1, fd, opt,

55
helper.go

@ -6,6 +6,7 @@ import (
"crypto/sha1"
"errors"
"fmt"
"github.com/mozillazg/go-httpheader"
"hash/crc64"
"io"
"net/http"
@ -237,3 +238,57 @@ func cloneObjectUploadPartOptions(opt *ObjectUploadPartOptions) *ObjectUploadPar
}
return &res
}
type RangeOptions struct {
HasStart bool
HasEnd bool
Start int64
End int64
}
func FormatRangeOptions(opt *RangeOptions) string {
if opt == nil {
return ""
}
if opt.HasStart && opt.HasEnd {
return fmt.Sprintf("bytes=%v-%v", opt.Start, opt.End)
}
if opt.HasStart {
return fmt.Sprintf("bytes=%v-", opt.Start)
}
if opt.HasEnd {
return fmt.Sprintf("bytes=-%v", opt.End)
}
return "bytes=-"
}
var deliverHeader = map[string]bool{}
func isDeliverHeader(key string) bool {
for k, v := range deliverHeader {
if key == k && v {
return true
}
}
return strings.HasPrefix(key, privateHeaderPrefix)
}
func deliverInitOptions(opt *InitiateMultipartUploadOptions) (*http.Header, error) {
if opt == nil {
return nil, nil
}
h, err := httpheader.Header(opt)
if err != nil {
return nil, err
}
header := &http.Header{}
for key, values := range h {
key = strings.ToLower(key)
if isDeliverHeader(key) {
for _, value := range values {
header.Add(key, value)
}
}
}
return header, nil
}

204
object.go

@ -182,6 +182,9 @@ type ObjectPutOptions struct {
//
// https://www.qcloud.com/document/product/436/7749
func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, uopt *ObjectPutOptions) (*Response, error) {
if r == nil {
return nil, fmt.Errorf("reader is nil")
}
if err := CheckReaderLen(r); err != nil {
return nil, err
}
@ -550,6 +553,12 @@ type MultiUploadOptions struct {
EnableVerification bool
}
type MultiDownloadOptions struct {
Opt *ObjectGetOptions
PartSize int64
ThreadPoolSize int
}
type Chunk struct {
Number int
OffSet int64
@ -567,6 +576,7 @@ type Jobs struct {
Chunk Chunk
Data io.Reader
Opt *ObjectUploadPartOptions
DownOpt *ObjectGetOptions
}
type Results struct {
@ -629,6 +639,49 @@ func worker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
}
}
func downloadWorker(s *ObjectService, jobs <-chan *Jobs, results chan<- *Results) {
for j := range jobs {
opt := &RangeOptions{
HasStart: true,
HasEnd: true,
Start: j.Chunk.OffSet,
End: j.Chunk.OffSet + j.Chunk.Size - 1,
}
j.DownOpt.Range = FormatRangeOptions(opt)
rt := j.RetryTimes
for {
var res Results
res.PartNumber = j.Chunk.Number
resp, err := s.Get(context.Background(), j.Name, j.DownOpt)
res.err = err
res.Resp = resp
if err != nil {
rt--
if rt == 0 {
results <- &res
break
}
continue
}
defer resp.Body.Close()
fd, err := os.OpenFile(j.FilePath, os.O_WRONLY, 0660)
if err != nil {
res.err = err
results <- &res
break
}
fd.Seek(j.Chunk.OffSet, os.SEEK_SET)
n, err := io.Copy(fd, LimitReadCloser(resp.Body, j.Chunk.Size))
if n != j.Chunk.Size || err != nil {
res.err = fmt.Errorf("io.Copy Failed, nread:%v, want:%v, err:%v", n, j.Chunk.Size, err)
}
fd.Close()
results <- &res
break
}
}
}
func DividePart(fileSize int64, last int) (int64, int64) {
partSize := int64(last * 1024 * 1024)
partNum := fileSize / partSize
@ -861,6 +914,7 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
var consumedBytes int64
if opt.OptIni != nil {
listener = opt.OptIni.Listener
optcom.XOptionHeader, _ = deliverInitOptions(opt.OptIni)
}
event := newProgressEvent(ProgressStartedEvent, 0, 0, totalBytes)
progressCallback(listener, event)
@ -950,6 +1004,156 @@ func (s *ObjectService) Upload(ctx context.Context, name string, filepath string
return v, resp, err
}
func SplitSizeIntoChunks(totalBytes int64, partSize int64) ([]Chunk, int, error) {
var partNum int64
if partSize > 0 {
partSize = partSize * 1024 * 1024
partNum = totalBytes / partSize
if partNum >= 10000 {
return nil, 0, errors.New("Too manry parts, out of 10000")
}
} else {
partNum, partSize = DividePart(totalBytes, 64)
}
var chunks []Chunk
var chunk = Chunk{}
for i := int64(0); i < partNum; i++ {
chunk.Number = int(i + 1)
chunk.OffSet = i * partSize
chunk.Size = partSize
chunks = append(chunks, chunk)
}
if totalBytes%partSize > 0 {
chunk.Number = len(chunks) + 1
chunk.OffSet = int64(len(chunks)) * partSize
chunk.Size = totalBytes % partSize
chunks = append(chunks, chunk)
partNum++
}
return chunks, int(partNum), nil
}
func (s *ObjectService) Download(ctx context.Context, name string, filepath string, opt *MultiDownloadOptions) (*Response, error) {
// 参数校验
if opt == nil {
opt = &MultiDownloadOptions{}
}
if opt.Opt != nil && opt.Opt.Range != "" {
return nil, fmt.Errorf("Download doesn't support Range Options")
}
// 获取文件长度和CRC
var coscrc string
resp, err := s.Head(ctx, name, nil)
if err != nil {
return resp, err
}
// 如果对象不存在x-cos-hash-crc64ecma,则跳过不做校验
coscrc = resp.Header.Get("x-cos-hash-crc64ecma")
strTotalBytes := resp.Header.Get("Content-Length")
totalBytes, err := strconv.ParseInt(strTotalBytes, 10, 64)
if err != nil {
return resp, err
}
// 切分
chunks, partNum, err := SplitSizeIntoChunks(totalBytes, opt.PartSize)
if err != nil {
return resp, err
}
// 直接下载到文件
if partNum == 0 || partNum == 1 {
rsp, err := s.GetToFile(ctx, name, filepath, opt.Opt)
if err != nil {
return rsp, err
}
if coscrc != "" && s.client.Conf.EnableCRC {
icoscrc, _ := strconv.ParseUint(coscrc, 10, 64)
fd, err := os.Open(filepath)
if err != nil {
return rsp, err
}
defer fd.Close()
localcrc, err := calCRC64(fd)
if err != nil {
return rsp, err
}
if localcrc != icoscrc {
return rsp, fmt.Errorf("verification failed, want:%v, return:%v", icoscrc, localcrc)
}
}
return rsp, err
}
// 创建文件
nfile, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return resp, err
}
nfile.Close()
var poolSize int
if opt.ThreadPoolSize > 0 {
poolSize = opt.ThreadPoolSize
} else {
poolSize = 1
}
chjobs := make(chan *Jobs, 100)
chresults := make(chan *Results, 10000)
for w := 1; w <= poolSize; w++ {
go downloadWorker(s, chjobs, chresults)
}
go func() {
for _, chunk := range chunks {
var downOpt ObjectGetOptions
if opt.Opt != nil {
downOpt = *opt.Opt
downOpt.Listener = nil // listener need to set nil
}
job := &Jobs{
Name: name,
RetryTimes: 3,
FilePath: filepath,
Chunk: chunk,
DownOpt: &downOpt,
}
chjobs <- job
}
close(chjobs)
}()
err = nil
for i := 0; i < partNum; i++ {
res := <-chresults
if res.Resp == nil || res.err != nil {
err = fmt.Errorf("part %d get resp Content. error: %s", res.PartNumber, res.err.Error())
continue
}
}
close(chresults)
if err != nil {
return nil, err
}
if coscrc != "" && s.client.Conf.EnableCRC {
icoscrc, _ := strconv.ParseUint(coscrc, 10, 64)
fd, err := os.Open(filepath)
if err != nil {
return resp, err
}
defer fd.Close()
localcrc, err := calCRC64(fd)
if err != nil {
return resp, err
}
if localcrc != icoscrc {
return resp, fmt.Errorf("verification failed, want:%v, return:%v", icoscrc, localcrc)
}
}
return resp, err
}
type ObjectPutTaggingOptions struct {
XMLName xml.Name `xml:"Tagging"`
TagSet []ObjectTaggingTag `xml:"TagSet>Tag,omitempty"`

3
object_part.go

@ -70,6 +70,9 @@ type ObjectUploadPartOptions struct {
//
// https://www.qcloud.com/document/product/436/7750
func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, uopt *ObjectUploadPartOptions) (*Response, error) {
if r == nil {
return nil, fmt.Errorf("reader is nil")
}
if err := CheckReaderLen(r); err != nil {
return nil, err
}

76
object_test.go

@ -8,12 +8,15 @@ import (
"encoding/xml"
"fmt"
"hash/crc64"
"io"
"io/ioutil"
math_rand "math/rand"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
)
@ -514,3 +517,76 @@ func TestObjectService_Upload2(t *testing.T) {
retry++
}
}
func TestObjectService_Download(t *testing.T) {
setup()
defer teardown()
filePath := "rsp.file" + time.Now().Format(time.RFC3339)
newfile, err := os.Create(filePath)
if err != nil {
t.Fatalf("create tmp file failed")
}
defer os.Remove(filePath)
// 源文件内容
totalBytes := int64(1024*1024*9 + 123)
b := make([]byte, totalBytes)
_, err = rand.Read(b)
newfile.Write(b)
newfile.Close()
tb := crc64.MakeTable(crc64.ECMA)
localcrc := strconv.FormatUint(crc64.Update(0, tb, b), 10)
retryMap := make(map[int64]int)
mux.HandleFunc("/test.go.download", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.Header().Add("Content-Length", strconv.FormatInt(totalBytes, 10))
w.Header().Add("x-cos-hash-crc64ecma", localcrc)
return
}
strRange := r.Header.Get("Range")
slice1 := strings.Split(strRange, "=")
slice2 := strings.Split(slice1[1], "-")
start, _ := strconv.ParseInt(slice2[0], 10, 64)
end, _ := strconv.ParseInt(slice2[1], 10, 64)
if retryMap[start] == 0 {
// 重试校验1
retryMap[start]++
w.WriteHeader(http.StatusGatewayTimeout)
} else if retryMap[start] == 1 {
// 重试检验2
retryMap[start]++
io.Copy(w, bytes.NewBuffer(b[start:end]))
} else if retryMap[start] == 2 {
// 重试检验3
retryMap[start]++
st := math_rand.Int63n(totalBytes - 1024*1024)
et := st + end - start
io.Copy(w, bytes.NewBuffer(b[st:et+1]))
} else {
io.Copy(w, bytes.NewBuffer(b[start:end+1]))
}
})
opt := &MultiDownloadOptions{
ThreadPoolSize: 3,
PartSize: 1,
}
downPath := "down.file" + time.Now().Format(time.RFC3339)
defer os.Remove(downPath)
_, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
if err == nil {
// 长度不一致 Failed
t.Fatalf("Object.Upload returned error: %v", err)
}
_, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
if err == nil {
// CRC不一致
t.Fatalf("Object.Upload returned error: %v", err)
}
_, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
if err != nil {
// 正确
t.Fatalf("Object.Upload returned error: %v", err)
}
}
Loading…
Cancel
Save