소스 검색

Merge pull request #150 from aliyun/preview_1.9.5

Preview 1.9.5
fengyu 6 년 전
부모
커밋
4502d8167f
8개의 변경된 파일711개의 추가작업 그리고 35개의 파일을 삭제
  1. 3 0
      .travis.yml
  2. 480 10
      oss/bucket_test.go
  3. 11 0
      oss/client.go
  4. 34 3
      oss/client_test.go
  5. 41 21
      oss/conf.go
  6. 23 1
      oss/conn.go
  7. 28 0
      oss/limit_reader_1_6.go
  8. 91 0
      oss/limit_reader_1_7.go

+ 3 - 0
.travis.yml

@@ -10,6 +10,9 @@ install:
 - go get gopkg.in/check.v1
 - go get github.com/satori/go.uuid
 - go get github.com/baiyubin/aliyun-sts-go-sdk/sts
+
+- if [[ $TRAVIS_GO_VERSION = '1.7' || $TRAVIS_GO_VERSION > '1.7' ]]; then go get golang.org/x/time/rate ; fi
+ 
 script:
 - cd oss
 - travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m

+ 480 - 10
oss/bucket_test.go

@@ -59,7 +59,6 @@ func (s *OssBucketSuite) SetUpSuite(c *C) {
 	testLogger.Println("test bucket started")
 }
 
-
 // TearDownSuite runs before each test or benchmark starts running.
 func (s *OssBucketSuite) TearDownSuite(c *C) {
 	for _, bucket := range []*Bucket{s.bucket, s.archiveBucket} {
@@ -1556,25 +1555,26 @@ func (s *OssBucketSuite) TestCopyObject(c *C) {
 
 // TestCopyObjectToOrFrom
 func (s *OssBucketSuite) TestCopyObjectToOrFrom(c *C) {
-	objectName := objectNamePrefix + "tcotof"
+	objectName := objectNamePrefix + "tcotof" + randLowStr(5)
 	objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?"
-	destBucket := bucketName + "-dest"
+	destBucketName := bucketName + "-dest" + randLowStr(5)
 	objectNameDest := objectName + "dest"
 
-	s.client.CreateBucket(destBucket)
+	err := s.client.CreateBucket(destBucketName)
+	c.Assert(err, IsNil)
 
-	destBuck, err := s.client.Bucket(destBucket)
+	destBucket, err := s.client.Bucket(destBucketName)
 	c.Assert(err, IsNil)
 
 	err = s.bucket.PutObject(objectName, strings.NewReader(objectValue))
 	c.Assert(err, IsNil)
 
 	// Copy from
-	_, err = destBuck.CopyObjectFrom(bucketName, objectName, objectNameDest)
+	_, err = destBucket.CopyObjectFrom(bucketName, objectName, objectNameDest)
 	c.Assert(err, IsNil)
 
 	// Check
-	body, err := destBuck.GetObject(objectNameDest)
+	body, err := destBucket.GetObject(objectNameDest)
 	c.Assert(err, IsNil)
 	str, err := readBody(body)
 	c.Assert(err, IsNil)
@@ -1584,7 +1584,7 @@ func (s *OssBucketSuite) TestCopyObjectToOrFrom(c *C) {
 	c.Assert(err, IsNil)
 
 	// Copy to
-	_, err = destBuck.CopyObjectTo(bucketName, objectName, objectNameDest)
+	_, err = destBucket.CopyObjectTo(bucketName, objectName, objectNameDest)
 	c.Assert(err, IsNil)
 
 	// Check
@@ -1595,13 +1595,13 @@ func (s *OssBucketSuite) TestCopyObjectToOrFrom(c *C) {
 	c.Assert(str, Equals, objectValue)
 
 	// Clean
-	err = destBuck.DeleteObject(objectNameDest)
+	err = destBucket.DeleteObject(objectNameDest)
 	c.Assert(err, IsNil)
 
 	err = s.bucket.DeleteObject(objectName)
 	c.Assert(err, IsNil)
 
-	err = s.client.DeleteBucket(destBucket)
+	err = s.client.DeleteBucket(destBucketName)
 	c.Assert(err, IsNil)
 }
 
@@ -2269,3 +2269,473 @@ func (s *OssBucketSuite) getObject(objects []ObjectProperties, object string) (b
 	}
 	return false, ObjectProperties{}
 }
+
+func (s *OssBucketSuite) detectUploadSpeed(bucket *Bucket, c *C) (upSpeed int) {
+	objectName := objectNamePrefix + getUuid()
+
+	// 1M byte
+	textBuffer := randStr(1024 * 1024)
+
+	// Put string
+	startT := time.Now()
+	err := bucket.PutObject(objectName, strings.NewReader(textBuffer))
+	endT := time.Now()
+
+	c.Assert(err, IsNil)
+	err = bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// byte/s
+	upSpeed = len(textBuffer) * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+	return upSpeed
+}
+
+func (s *OssBucketSuite) TestPutSingleObjectLimitSpeed(c *C) {
+
+	// create client and bucket
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.LimitUploadSpeed(1)
+	if err != nil {
+		// go version is less than go1.7,not support limit upload speed
+		// doesn't run this test
+		return
+	} else {
+		// set unlimited again
+		client.LimitUploadSpeed(0)
+	}
+
+	bucketName := bucketNamePrefix + randLowStr(5)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	//detect speed:byte/s
+	detectSpeed := s.detectUploadSpeed(bucket, c)
+
+	var limitSpeed = 0
+	if detectSpeed <= perTokenBandwidthSize*2 {
+		limitSpeed = perTokenBandwidthSize
+	} else {
+		//this situation, the test works better
+		limitSpeed = detectSpeed / 2
+	}
+
+	// KB/s
+	err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+	c.Assert(err, IsNil)
+
+	objectName := objectNamePrefix + getUuid()
+
+	// 1M byte
+	textBuffer := randStr(1024 * 1024)
+
+	// Put body
+	startT := time.Now()
+	err = bucket.PutObject(objectName, strings.NewReader(textBuffer))
+	endT := time.Now()
+
+	realSpeed := int64(len(textBuffer)) * 1000 / (endT.UnixNano()/1000/1000 - startT.UnixNano()/1000/1000)
+
+	fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+	c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+	if detectSpeed > perTokenBandwidthSize {
+		// the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+		c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+	}
+
+	// Get object and compare content
+	body, err := bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, textBuffer)
+
+	bucket.DeleteObject(objectName)
+	client.DeleteBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	return
+}
+
+func putObjectRoutin(bucket *Bucket, object string, textBuffer *string, notifyChan chan int) error {
+	err := bucket.PutObject(object, strings.NewReader(*textBuffer))
+	if err == nil {
+		notifyChan <- 1
+	} else {
+		notifyChan <- 0
+	}
+	return err
+}
+
+func (s *OssBucketSuite) TestPutManyObjectLimitSpeed(c *C) {
+
+	// create client and bucket
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.LimitUploadSpeed(1)
+	if err != nil {
+		// go version is less than go1.7,not support limit upload speed
+		// doesn't run this test
+		return
+	} else {
+		// set unlimited
+		client.LimitUploadSpeed(0)
+	}
+
+	bucketName := bucketNamePrefix + randLowStr(5)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	//detect speed:byte/s
+	detectSpeed := s.detectUploadSpeed(bucket, c)
+	var limitSpeed = 0
+	if detectSpeed <= perTokenBandwidthSize*2 {
+		limitSpeed = perTokenBandwidthSize
+	} else {
+		limitSpeed = detectSpeed / 2
+	}
+
+	// KB/s
+	err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+	c.Assert(err, IsNil)
+
+	// object1
+	objectNameFirst := objectNamePrefix + getUuid()
+	objectNameSecond := objectNamePrefix + getUuid()
+
+	// 1M byte
+	textBuffer := randStr(1024 * 1024)
+
+	objectCount := 2
+	notifyChan := make(chan int, objectCount)
+
+	//start routin
+	startT := time.Now()
+	go putObjectRoutin(bucket, objectNameFirst, &textBuffer, notifyChan)
+	go putObjectRoutin(bucket, objectNameSecond, &textBuffer, notifyChan)
+
+	// wait routin end
+	sum := int(0)
+	for j := 0; j < objectCount; j++ {
+		result := <-notifyChan
+		sum += result
+	}
+	endT := time.Now()
+
+	realSpeed := len(textBuffer) * 2 * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+	c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+	if detectSpeed > perTokenBandwidthSize {
+		// the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+		c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+	}
+	c.Assert(sum, Equals, 2)
+
+	// Get object and compare content
+	body, err := bucket.GetObject(objectNameFirst)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, textBuffer)
+
+	body, err = bucket.GetObject(objectNameSecond)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, textBuffer)
+
+	// clear bucket and object
+	bucket.DeleteObject(objectNameFirst)
+	bucket.DeleteObject(objectNameSecond)
+	client.DeleteBucket(bucketName)
+
+	fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+	return
+}
+
+func (s *OssBucketSuite) TestPutMultipartObjectLimitSpeed(c *C) {
+
+	// create client and bucket
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.LimitUploadSpeed(1)
+	if err != nil {
+		// go version is less than go1.7,not support limit upload speed
+		// doesn't run this test
+		return
+	} else {
+		// set unlimited
+		client.LimitUploadSpeed(0)
+	}
+
+	bucketName := bucketNamePrefix + randLowStr(5)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	//detect speed:byte/s
+	detectSpeed := s.detectUploadSpeed(bucket, c)
+
+	var limitSpeed = 0
+	if detectSpeed <= perTokenBandwidthSize*2 {
+		limitSpeed = perTokenBandwidthSize
+	} else {
+		//this situation, the test works better
+		limitSpeed = detectSpeed / 2
+	}
+
+	// KB/s
+	err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+	c.Assert(err, IsNil)
+
+	objectName := objectNamePrefix + getUuid()
+	fileName := "." + string(os.PathSeparator) + objectName
+
+	// 1M byte
+	fileSize := 0
+	textBuffer := randStr(1024 * 1024)
+	if detectSpeed < perTokenBandwidthSize {
+		ioutil.WriteFile(fileName, []byte(textBuffer), 0644)
+		f, err := os.Stat(fileName)
+		c.Assert(err, IsNil)
+
+		fileSize = int(f.Size())
+		c.Assert(fileSize, Equals, len(textBuffer))
+
+	} else {
+		loopCount := 5
+		f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
+		c.Assert(err, IsNil)
+
+		for i := 0; i < loopCount; i++ {
+			f.Write([]byte(textBuffer))
+		}
+
+		fileInfo, err := f.Stat()
+		c.Assert(err, IsNil)
+
+		fileSize = int(fileInfo.Size())
+		c.Assert(fileSize, Equals, len(textBuffer)*loopCount)
+
+		f.Close()
+	}
+
+	// Put body
+	startT := time.Now()
+	err = bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, ""))
+	endT := time.Now()
+
+	c.Assert(err, IsNil)
+	realSpeed := fileSize * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+	c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+	if detectSpeed > perTokenBandwidthSize {
+		// the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+		c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+	}
+
+	// Get object and compare content
+	body, err := bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+
+	fileBody, err := ioutil.ReadFile(fileName)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, string(fileBody))
+
+	// delete bucket、object、file
+	bucket.DeleteObject(objectName)
+	client.DeleteBucket(bucketName)
+	os.Remove(fileName)
+
+	fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+	return
+}
+
+func (s *OssBucketSuite) TestPutObjectFromFileLimitSpeed(c *C) {
+	// create client and bucket
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.LimitUploadSpeed(1)
+	if err != nil {
+		// go version is less than go1.7,not support limit upload speed
+		// doesn't run this test
+		return
+	} else {
+		// set unlimited
+		client.LimitUploadSpeed(0)
+	}
+
+	bucketName := bucketNamePrefix + randLowStr(5)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	//detect speed:byte/s
+	detectSpeed := s.detectUploadSpeed(bucket, c)
+
+	var limitSpeed = 0
+	if detectSpeed <= perTokenBandwidthSize*2 {
+		limitSpeed = perTokenBandwidthSize
+	} else {
+		//this situation, the test works better
+		limitSpeed = detectSpeed / 2
+	}
+
+	// KB/s
+	err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize)
+	c.Assert(err, IsNil)
+
+	objectName := objectNamePrefix + getUuid()
+	fileName := "." + string(os.PathSeparator) + objectName
+
+	// 1M byte
+	fileSize := 0
+	textBuffer := randStr(1024 * 1024)
+	if detectSpeed < perTokenBandwidthSize {
+		ioutil.WriteFile(fileName, []byte(textBuffer), 0644)
+		f, err := os.Stat(fileName)
+		c.Assert(err, IsNil)
+
+		fileSize = int(f.Size())
+		c.Assert(fileSize, Equals, len(textBuffer))
+
+	} else {
+		loopCount := 2
+		f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
+		c.Assert(err, IsNil)
+
+		for i := 0; i < loopCount; i++ {
+			f.Write([]byte(textBuffer))
+		}
+
+		fileInfo, err := f.Stat()
+		c.Assert(err, IsNil)
+
+		fileSize = int(fileInfo.Size())
+		c.Assert(fileSize, Equals, len(textBuffer)*loopCount)
+
+		f.Close()
+	}
+
+	// Put body
+	startT := time.Now()
+	err = bucket.PutObjectFromFile(objectName, fileName)
+	endT := time.Now()
+
+	c.Assert(err, IsNil)
+	realSpeed := fileSize * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+	c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true)
+
+	if detectSpeed > perTokenBandwidthSize {
+		// the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s)
+		c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true)
+	}
+
+	// Get object and compare content
+	body, err := bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+
+	fileBody, err := ioutil.ReadFile(fileName)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, string(fileBody))
+
+	// delete bucket、file、object
+	bucket.DeleteObject(objectName)
+	client.DeleteBucket(bucketName)
+	os.Remove(fileName)
+
+	fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed)
+
+	return
+}
+
+// upload speed limit parameters will not affect download speed
+func (s *OssBucketSuite) TestUploadObjectLimitSpeed(c *C) {
+	// create limit client and bucket
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	tokenCount := 1
+	err = client.LimitUploadSpeed(tokenCount)
+	if err != nil {
+		// go version is less than go1.7,not support limit upload speed
+		// doesn't run this test
+		return
+	} else {
+		// set unlimited
+		client.LimitUploadSpeed(0)
+	}
+
+	bucketName := bucketNamePrefix + randLowStr(5)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+	c.Assert(err, IsNil)
+
+	//first:upload a object
+	textBuffer := randStr(1024 * 100)
+	objectName := objectNamePrefix + getUuid()
+	err = bucket.PutObject(objectName, strings.NewReader(textBuffer))
+	c.Assert(err, IsNil)
+
+	// limit upload speed
+	err = client.LimitUploadSpeed(tokenCount)
+	c.Assert(err, IsNil)
+
+	// then download the object
+	startT := time.Now()
+	body, err := bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	endT := time.Now()
+
+	c.Assert(str, Equals, textBuffer)
+
+	// byte/s
+	downloadSpeed := len(textBuffer) * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000)
+
+	// upload speed limit parameters will not affect download speed
+	c.Assert(downloadSpeed > 2*tokenCount*perTokenBandwidthSize, Equals, true)
+
+	bucket.DeleteObject(objectName)
+	client.DeleteBucket(bucketName)
+}
+
+// test LimitUploadSpeed failure
+func (s *OssBucketSuite) TestLimitUploadSpeedFail(c *C) {
+	// create limit client and bucket
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.LimitUploadSpeed(-1)
+	c.Assert(err, NotNil)
+
+	client.Config = nil
+	err = client.LimitUploadSpeed(100)
+	c.Assert(err, NotNil)
+}

+ 11 - 0
oss/client.go

@@ -5,6 +5,7 @@ package oss
 import (
 	"bytes"
 	"encoding/xml"
+	"fmt"
 	"io"
 	"log"
 	"net/http"
@@ -649,6 +650,16 @@ func (client Client) GetBucketInfo(bucketName string) (GetBucketInfoResult, erro
 	return out, err
 }
 
+// LimitUploadSpeed: set upload bandwidth limit speed,default is 0,unlimited
+// upSpeed: KB/s, 0 is unlimited,default is 0
+// error:it's nil if success, otherwise failure
+func (client Client) LimitUploadSpeed(upSpeed int) error {
+	if client.Config == nil {
+		return fmt.Errorf("client config is nil")
+	}
+	return client.Config.LimitUploadSpeed(upSpeed)
+}
+
 // UseCname sets the flag of using CName. By default it's false.
 //
 // isUseCname    true: the endpoint has the CName, false: the endpoint does not have cname. Default is false.

+ 34 - 3
oss/client_test.go

@@ -10,10 +10,12 @@ import (
 	"math/rand"
 	"net/http"
 	"os"
+	"runtime"
 	"strings"
 	"testing"
 	"time"
 
+	uuid "github.com/satori/go.uuid"
 	. "gopkg.in/check.v1"
 )
 
@@ -43,12 +45,12 @@ var (
 	stsARN       = os.Getenv("OSS_TEST_STS_ARN")
 )
 
-const (
+var (
 	// prefix of bucket name for bucket ops test
 	bucketNamePrefix = "go-sdk-test-bucket-abcx-"
 	// bucket name for object ops test
-	bucketName        = "go-sdk-test-bucket-abcx-for-object"
-	archiveBucketName = "go-sdk-test-bucket-abcx-for-archive"
+	bucketName        = "go-sdk-test-bucket-abcx-for-object" + randLowStr(5)
+	archiveBucketName = "go-sdk-test-bucket-abcx-for-archive" + randLowStr(5)
 	// object name for object ops test
 	objectNamePrefix = "go-sdk-test-object-abcx-"
 	// sts region is one and only hangzhou
@@ -83,6 +85,12 @@ func randLowStr(n int) string {
 	return strings.ToLower(randStr(n))
 }
 
+func getUuid() string {
+	uniqId, _ := uuid.NewV4()
+	uniqKey := uniqId.String()
+	return uniqKey
+}
+
 // SetUpSuite runs once when the suite starts running
 func (s *OssClientSuite) SetUpSuite(c *C) {
 	client, err := New(endpoint, accessID, accessKey)
@@ -1586,3 +1594,26 @@ func (s *OssClientSuite) TestHttpLogSignUrl(c *C) {
 	os.Remove(logName)
 	client.DeleteBucket(testBucketName)
 }
+
+func (s *OssClientSuite) TestSetLimitUploadSpeed(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	err = client.LimitUploadSpeed(100)
+
+	goVersion := runtime.Version()
+	pSlice := strings.Split(strings.ToLower(goVersion), ".")
+
+	// compare with go1.7
+	if len(pSlice) >= 2 {
+		if pSlice[0] > "go1" {
+			c.Assert(err, IsNil)
+		} else if pSlice[0] == "go1" && pSlice[1] >= "7" {
+			c.Assert(err, IsNil)
+		} else {
+			c.Assert(err, NotNil)
+		}
+	} else {
+		c.Assert(err, NotNil)
+	}
+}

+ 41 - 21
oss/conf.go

@@ -34,27 +34,47 @@ type HTTPMaxConns struct {
 
 // Config defines oss configuration
 type Config struct {
-	Endpoint        string       // OSS endpoint
-	AccessKeyID     string       // AccessId
-	AccessKeySecret string       // AccessKey
-	RetryTimes      uint         // Retry count by default it's 5.
-	UserAgent       string       // SDK name/version/system information
-	IsDebug         bool         // Enable debug mode. Default is false.
-	Timeout         uint         // Timeout in seconds. By default it's 60.
-	SecurityToken   string       // STS Token
-	IsCname         bool         // If cname is in the endpoint.
-	HTTPTimeout     HTTPTimeout  // HTTP timeout
-	HTTPMaxConns    HTTPMaxConns // Http max connections
-	IsUseProxy      bool         // Flag of using proxy.
-	ProxyHost       string       // Flag of using proxy host.
-	IsAuthProxy     bool         // Flag of needing authentication.
-	ProxyUser       string       // Proxy user
-	ProxyPassword   string       // Proxy password
-	IsEnableMD5     bool         // Flag of enabling MD5 for upload.
-	MD5Threshold    int64        // Memory footprint threshold for each MD5 computation (16MB is the default), in byte. When the data is more than that, temp file is used.
-	IsEnableCRC     bool         // Flag of enabling CRC for upload.
-	LogLevel        int          // log level
-	Logger          *log.Logger  // For write log
+	Endpoint         string       // OSS endpoint
+	AccessKeyID      string       // AccessId
+	AccessKeySecret  string       // AccessKey
+	RetryTimes       uint         // Retry count by default it's 5.
+	UserAgent        string       // SDK name/version/system information
+	IsDebug          bool         // Enable debug mode. Default is false.
+	Timeout          uint         // Timeout in seconds. By default it's 60.
+	SecurityToken    string       // STS Token
+	IsCname          bool         // If cname is in the endpoint.
+	HTTPTimeout      HTTPTimeout  // HTTP timeout
+	HTTPMaxConns     HTTPMaxConns // Http max connections
+	IsUseProxy       bool         // Flag of using proxy.
+	ProxyHost        string       // Flag of using proxy host.
+	IsAuthProxy      bool         // Flag of needing authentication.
+	ProxyUser        string       // Proxy user
+	ProxyPassword    string       // Proxy password
+	IsEnableMD5      bool         // Flag of enabling MD5 for upload.
+	MD5Threshold     int64        // Memory footprint threshold for each MD5 computation (16MB is the default), in byte. When the data is more than that, temp file is used.
+	IsEnableCRC      bool         // Flag of enabling CRC for upload.
+	LogLevel         int          // Log level
+	Logger           *log.Logger  // For write log
+	UploadLimitSpeed int          // Upload limit speed:KB/s, 0 is unlimited
+	UploadLimiter    *OssLimiter  // Bandwidth limit reader for upload
+}
+
+// LimitUploadSpeed, uploadSpeed:KB/s, 0 is unlimited,default is 0
+func (config *Config) LimitUploadSpeed(uploadSpeed int) error {
+	if uploadSpeed < 0 {
+		return fmt.Errorf("erro,speed is less than 0")
+	} else if uploadSpeed == 0 {
+		config.UploadLimitSpeed = 0
+		config.UploadLimiter = nil
+		return nil
+	}
+
+	var err error
+	config.UploadLimiter, err = GetOssLimiter(uploadSpeed)
+	if err == nil {
+		config.UploadLimitSpeed = uploadSpeed
+	}
+	return err
 }
 
 // WriteLog

+ 23 - 1
oss/conn.go

@@ -365,11 +365,33 @@ func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64,
 	if !ok && reader != nil {
 		rc = ioutil.NopCloser(reader)
 	}
-	req.Body = rc
 
+	if conn.isUploadLimitReq(req) {
+		limitReader := &LimitSpeedReader{
+			reader:     rc,
+			ossLimiter: conn.config.UploadLimiter,
+		}
+		req.Body = limitReader
+	} else {
+		req.Body = rc
+	}
 	return file, crc
 }
 
+// isUploadLimitReq: judge limit upload speed or not
+func (conn Conn) isUploadLimitReq(req *http.Request) bool {
+	if conn.config.UploadLimitSpeed == 0 || conn.config.UploadLimiter == nil {
+		return false
+	}
+
+	if req.Method != "GET" && req.Method != "DELETE" && req.Method != "HEAD" {
+		if req.ContentLength > 0 {
+			return true
+		}
+	}
+	return false
+}
+
 func tryGetFileSize(f *os.File) int64 {
 	fInfo, _ := f.Stat()
 	return fInfo.Size()

+ 28 - 0
oss/limit_reader_1_6.go

@@ -0,0 +1,28 @@
+// +build !go1.7
+
+// "golang.org/x/time/rate" is depended on golang context package  go1.7 onward
+// this file is only for build,not supports limit upload speed
+package oss
+
+import (
+	"fmt"
+	"io"
+)
+
+const (
+	perTokenBandwidthSize int = 1024
+)
+
+type OssLimiter struct {
+}
+
+type LimitSpeedReader struct {
+	io.ReadCloser
+	reader     io.Reader
+	ossLimiter *OssLimiter
+}
+
+func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) {
+	err = fmt.Errorf("rate.Limiter is not supported below version go1.7")
+	return nil, err
+}

+ 91 - 0
oss/limit_reader_1_7.go

@@ -0,0 +1,91 @@
+// +build go1.7
+
+package oss
+
+import (
+	"fmt"
+	"io"
+	"math"
+	"time"
+
+	"golang.org/x/time/rate"
+)
+
+const (
+	perTokenBandwidthSize int = 1024
+)
+
+// OssLimiter: wrapper rate.Limiter
+type OssLimiter struct {
+	limiter *rate.Limiter
+}
+
+// GetOssLimiter:create OssLimiter
+// uploadSpeed:KB/s
+func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) {
+	limiter := rate.NewLimiter(rate.Limit(uploadSpeed), uploadSpeed)
+
+	// first consume the initial full token,the limiter will behave more accurately
+	limiter.AllowN(time.Now(), uploadSpeed)
+
+	return &OssLimiter{
+		limiter: limiter,
+	}, nil
+}
+
+// LimitSpeedReader: for limit bandwidth upload
+type LimitSpeedReader struct {
+	io.ReadCloser
+	reader     io.Reader
+	ossLimiter *OssLimiter
+}
+
+// Read
+func (r *LimitSpeedReader) Read(p []byte) (n int, err error) {
+	n = 0
+	err = nil
+	start := 0
+	burst := r.ossLimiter.limiter.Burst()
+	var end int
+	var tmpN int
+	var tc int
+	for start < len(p) {
+		if start+burst*perTokenBandwidthSize < len(p) {
+			end = start + burst*perTokenBandwidthSize
+		} else {
+			end = len(p)
+		}
+
+		tmpN, err = r.reader.Read(p[start:end])
+		if tmpN > 0 {
+			n += tmpN
+			start = n
+		}
+
+		if err != nil {
+			return
+		}
+
+		tc = int(math.Ceil(float64(tmpN) / float64(perTokenBandwidthSize)))
+		now := time.Now()
+		re := r.ossLimiter.limiter.ReserveN(now, tc)
+		if !re.OK() {
+			err = fmt.Errorf("LimitSpeedReader.Read() failure,ReserveN error,start:%d,end:%d,burst:%d,perTokenBandwidthSize:%d",
+				start, end, burst, perTokenBandwidthSize)
+			return
+		} else {
+			timeDelay := re.Delay()
+			time.Sleep(timeDelay)
+		}
+	}
+	return
+}
+
+// Close ...
+func (r *LimitSpeedReader) Close() error {
+	rc, ok := r.reader.(io.ReadCloser)
+	if ok {
+		return rc.Close()
+	}
+	return nil
+}