├── test_data
├── test.gzip.svg
├── helloworld.gzip.html
└── helloworld.html
├── obs
├── error.go
├── log.go
├── conf.go
├── util.go
├── auth.go
├── http.go
├── temporary.go
└── convert.go
├── go.mod
├── interface.go
├── utils_test.go
├── utils.go
├── conf
└── app.conf.example
├── upyun_test.go
├── qiniu_test.go
├── oss_test.go
├── cos_test.go
├── minio_test.go
├── bos_test.go
├── .gitignore
├── obs_test.go
├── README.md
├── upyun.go
├── bos.go
├── cos.go
├── oss.go
├── obs.go
├── minio.go
├── qiniu.go
├── LICENSE
└── go.sum
/test_data/test.gzip.svg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TruthHun/CloudStore/HEAD/test_data/test.gzip.svg
--------------------------------------------------------------------------------
/test_data/helloworld.gzip.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TruthHun/CloudStore/HEAD/test_data/helloworld.gzip.html
--------------------------------------------------------------------------------
/obs/error.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | )
7 |
8 | type ObsError struct {
9 | BaseModel
10 | Status string
11 | XMLName xml.Name `xml:"Error"`
12 | Code string `xml:"Code"`
13 | Message string `xml:"Message"`
14 | Resource string `xml:"Resource"`
15 | HostId string `xml:"HostId"`
16 | }
17 |
18 | func (err ObsError) Error() string {
19 | return fmt.Sprintf("obs: service returned error: Status=%s, Code=%s, Message=%s, RequestId=%s",
20 | err.Status, err.Code, err.Message, err.RequestId)
21 | }
22 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/TruthHun/CloudStore
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/aliyun/aliyun-oss-go-sdk v2.1.7+incompatible
7 | github.com/astaxie/beego v1.12.3
8 | github.com/baidubce/bce-sdk-go v0.9.57
9 | github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
10 | github.com/go-ini/ini v1.62.0 // indirect
11 | github.com/minio/minio-go v6.0.14+incompatible
12 | github.com/mitchellh/go-homedir v1.1.0 // indirect
13 | github.com/qiniu/api.v7/v7 v7.8.2
14 | github.com/satori/go.uuid v1.2.0 // indirect
15 | github.com/smartystreets/goconvey v1.6.4 // indirect
16 | github.com/tencentyun/cos-go-sdk-v5 v0.7.24
17 | github.com/upyun/go-sdk v2.1.0+incompatible
18 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
19 | gopkg.in/ini.v1 v1.62.0 // indirect
20 | )
21 |
--------------------------------------------------------------------------------
/interface.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type File struct {
8 | ModTime time.Time
9 | Name string
10 | Size int64
11 | IsDir bool
12 | Header map[string]string
13 | }
14 |
15 | type CloudStore interface {
16 | Delete(objects ...string) (err error) // 删除文件
17 | GetSignURL(object string, expire int64) (link string, err error) // 文件访问签名
18 | IsExist(object string) (err error) // 判断文件是否存在
19 | Lists(prefix string) (files []File, err error) // 文件前缀,列出文件
20 | Upload(tmpFile string, saveFile string, headers ...map[string]string) (err error) // 上传文件
21 | Download(object string, savePath string) (err error) // 下载文件
22 | GetInfo(object string) (info File, err error) // 获取指定文件信息
23 | }
24 |
--------------------------------------------------------------------------------
/utils_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import "testing"
4 |
5 | var (
6 | objectSVG = "test_data/test.svg" //未经过gzip压缩的svg图片
7 | objectSVGGzip = "test_data/test.gzip.svg" //gzip压缩后的svg图片
8 | objectHtml = "test_data/helloworld.html" //未经gzip压缩的HTML
9 | objectHtmlGzip = "test_data/helloworld.gzip.html" //gzip压缩后的HTML
10 | objectNotExist = "not exist object"
11 | objectPrefix = "test_data"
12 | objectDownload = "test_data/download.svg"
13 | headerGzip = map[string]string{"Content-Encoding": "gzip"}
14 | headerSVG = map[string]string{"Content-Type": "image/svg+xml"}
15 | headerHtml = map[string]string{"Content-Type": "text/html; charset=UTF-8"}
16 | )
17 |
18 | func TestCompressByGzip(t *testing.T) {
19 | err = CompressByGzip(objectSVG, objectSVGGzip)
20 | if err != nil {
21 | t.Error(err)
22 | }
23 | err = CompressByGzip(objectHtml, objectHtmlGzip)
24 | if err != nil {
25 | t.Error(err)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "crypto/md5"
7 | "encoding/hex"
8 | "encoding/json"
9 | "io/ioutil"
10 | "os"
11 | "strings"
12 | )
13 |
14 | var (
15 | sevenDays int64 = 7 * 24 * 3600
16 | )
17 |
18 | // 绝对路径,abs => absolute
19 | func objectAbs(object string) string {
20 | return "/" + strings.TrimLeft(object, " ./")
21 | }
22 |
23 | // 相对路径 rel => relative
24 | func objectRel(object string) string {
25 | return strings.TrimLeft(object, " ./")
26 | }
27 |
28 | // MD5 Crypt
29 | func MD5Crypt(str string) string {
30 | h := md5.New()
31 | h.Write([]byte(str))
32 | return hex.EncodeToString(h.Sum(nil))
33 | }
34 |
35 | func CompressByGzip(tmpFile, saveFile string) (err error) {
36 | var (
37 | input []byte
38 | buf bytes.Buffer
39 | )
40 | input, err = ioutil.ReadFile(tmpFile)
41 | if err != nil {
42 | return
43 | }
44 |
45 | writer, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
46 | defer writer.Close()
47 |
48 | writer.Write(input)
49 | writer.Flush()
50 |
51 | err = ioutil.WriteFile(saveFile, buf.Bytes(), os.ModePerm)
52 |
53 | return
54 | }
55 |
56 | func toJSON(v interface{}) (jsonStr string) {
57 | p, err := json.Marshal(v)
58 | if err != nil {
59 | return
60 | }
61 | return string(p)
62 | }
63 |
--------------------------------------------------------------------------------
/conf/app.conf.example:
--------------------------------------------------------------------------------
1 | [oss]
2 | accessKey =
3 | secretKey =
4 | endpoint = oss-cn-shenzhen.aliyuncs.com
5 | bucket = public-dochub
6 | domain = https://public-dochub.oss-cn-shenzhen.aliyuncs.com
7 |
8 | [bos]
9 | accessKey =
10 | secretKey =
11 | bucket = dochub
12 | endpoint = gz.bcebos.com
13 | domain = https://dochub.cdn.bcebos.com/
14 |
15 | [cos]
16 | accessKey =
17 | secretKey =
18 | bucket = dochub
19 | appID = 1251298948
20 | region = ap-guangzhou
21 | domain = https://dochub-1251298948.cos.ap-guangzhou.myqcloud.com
22 |
23 | [obs]
24 | accessKey =
25 | secretKey =
26 | bucket = dochub
27 | endpoint = obs.ap-southeast-1.myhuaweicloud.com
28 | domain = https://dochub.obs.ap-southeast-1.myhuaweicloud.com
29 |
30 | [upyun]
31 | bucket =
32 | operator =
33 | password =
34 | domain = http://dochub.test.upcdn.net
35 | secret = 123456
36 |
37 | [qiniu]
38 | accessKey =
39 | secretKey =
40 | bucket = dochub
41 | domain = http://pohp6456o.bkt.clouddn.com/
42 |
43 | [minio]
44 | accessKey =
45 | secretKey =
46 | endpoint = 127.0.0.1:9000
47 | bucket = dochub
48 | domain = http://127.0.0.1:9000
49 |
50 |
51 |
--------------------------------------------------------------------------------
/upyun_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/astaxie/beego"
11 | )
12 |
13 | var Up *UpYun
14 |
15 | func init() {
16 | bucket := beego.AppConfig.String("upyun::bucket")
17 | operator := beego.AppConfig.String("upyun::operator")
18 | password := beego.AppConfig.String("upyun::password")
19 | domain := strings.ToLower(beego.AppConfig.String("upyun::domain"))
20 | secret := strings.ToLower(beego.AppConfig.String("upyun::secret"))
21 | Up = NewUpYun(bucket, operator, password, domain, secret)
22 | }
23 |
24 | func TestUpYun(t *testing.T) {
25 | // upload
26 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
27 | err = Up.Upload(objectSVG, objectSVG, headerSVG)
28 | if err != nil {
29 | t.Error(err)
30 | }
31 | err = Up.Upload(objectSVGGzip, objectSVGGzip, headerGzip, headerSVG)
32 | if err != nil {
33 | t.Error(err)
34 | }
35 | t.Log("=====IsExist=====")
36 | t.Log(objectSVG, "is exist?(Y):", Up.IsExist(objectSVG) == nil)
37 | t.Log(objectNotExist, "is exist?(N):", Up.IsExist(objectNotExist) == nil)
38 | t.Log("=====Lists=====")
39 | if files, err := Up.Lists(objectPrefix); err != nil {
40 | t.Error(err)
41 | } else {
42 | t.Log(objectPrefix, ":", fmt.Sprintf("%+v", files))
43 | }
44 | t.Log("=====GetInfo=====")
45 | if info, err := Up.GetInfo(objectSVG); err != nil {
46 | t.Error(err.Error())
47 | } else {
48 | t.Log(fmt.Sprintf("%+v", info))
49 | }
50 | t.Log("=====Download=====")
51 | if err := Up.Download(objectSVG, objectDownload); err != nil {
52 | t.Error(err)
53 | } else {
54 | t.Log("download success")
55 | b, _ := ioutil.ReadFile(objectDownload)
56 | t.Log("Content:", string(b))
57 | os.Remove(objectDownload)
58 | }
59 | t.Log("====GetSignURL====")
60 | t.Log(Up.GetSignURL(objectSVG, 1200))
61 | t.Log(Up.GetSignURL(objectSVGGzip, 1200))
62 | t.Log("========Finished========")
63 | }
64 |
65 | func TestUpYun_Delete(t *testing.T) {
66 | if err := Up.Delete(objectSVG, objectSVGGzip); err != nil {
67 | t.Error(err)
68 | } else {
69 | t.Log("delete success")
70 | }
71 |
72 | if files, err := Up.Lists(objectPrefix); err != nil {
73 | t.Error(err)
74 | } else {
75 | t.Log(fmt.Sprintf("%+v", files))
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/qiniu_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/astaxie/beego"
11 | )
12 |
13 | var (
14 | objectQiniu = "qiniu.go"
15 | Qiniu *QINIU
16 | )
17 |
18 | func init() {
19 | key := beego.AppConfig.String("qiniu::accessKey")
20 | secret := beego.AppConfig.String("qiniu::secretKey")
21 | bucket := beego.AppConfig.String("qiniu::bucket")
22 | domain := strings.ToLower(beego.AppConfig.String("qiniu::domain"))
23 | Qiniu, err = NewQINIU(key, secret, bucket, domain)
24 | if err != nil {
25 | panic(err)
26 | }
27 | }
28 |
29 |
30 | func TestQINIU(t *testing.T) {
31 | // upload
32 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
33 | err = Qiniu.Upload(objectSVG, objectSVG,headerSVG)
34 | if err != nil {
35 | t.Error(err)
36 | }
37 | err = Qiniu.Upload(objectSVGGzip, objectSVGGzip, headerGzip, headerSVG)
38 | if err != nil {
39 | t.Error(err)
40 | }
41 | t.Log("=====IsExist=====")
42 | t.Log(objectSVG, "is exist?(Y):", Qiniu.IsExist(objectSVG) == nil)
43 | t.Log(objectNotExist, "is exist?(N):", Qiniu.IsExist(objectNotExist) == nil)
44 | t.Log("=====Lists=====")
45 | if files, err := Qiniu.Lists(objectPrefix); err != nil {
46 | t.Error(err)
47 | } else {
48 | t.Log(fmt.Sprintf("%+v", files))
49 | }
50 | t.Log("=====GetInfo=====")
51 | if info, err := Qiniu.GetInfo(objectSVG); err != nil {
52 | t.Error(err.Error())
53 | } else {
54 | t.Log(fmt.Sprintf("%+v", info))
55 | }
56 | t.Log("=====Download=====")
57 | if err := Qiniu.Download(objectSVG, objectDownload); err != nil {
58 | t.Error(err)
59 | } else {
60 | t.Log("download success")
61 | b, _ := ioutil.ReadFile(objectDownload)
62 | t.Log("Content:", string(b))
63 | os.Remove(objectDownload)
64 | }
65 | t.Log("====GetSignURL====")
66 | t.Log(Qiniu.GetSignURL(objectSVG, 1200))
67 | t.Log(Qiniu.GetSignURL(objectSVGGzip, 1200))
68 | t.Log("========Finished========")
69 | }
70 |
71 | func TestQINIU_Delete(t *testing.T) {
72 | if err := Qiniu.Delete(objectSVG, objectSVGGzip); err != nil {
73 | t.Error(err)
74 | } else {
75 | t.Log("delete success")
76 | }
77 |
78 | if files, err := Qiniu.Lists(objectPrefix); err != nil {
79 | t.Error(err)
80 | } else {
81 | t.Log(fmt.Sprintf("%+v", files))
82 | }
83 | }
--------------------------------------------------------------------------------
/oss_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/astaxie/beego"
11 | )
12 |
13 | var (
14 | O *OSS
15 | objectOSS = "oss.go"
16 | )
17 |
18 | func init() {
19 | var err error
20 |
21 | key := beego.AppConfig.String("oss::accessKey")
22 | secret := beego.AppConfig.String("oss::secretKey")
23 | endpoint := beego.AppConfig.String("oss::endpoint")
24 | bucket := beego.AppConfig.String("oss::bucket")
25 | domain := strings.ToLower(beego.AppConfig.String("oss::domain"))
26 |
27 | O, err = NewOSS(key, secret, endpoint, bucket, domain)
28 | if err != nil {
29 | panic(err)
30 | }
31 | }
32 |
33 |
34 |
35 | func TestOSS(t *testing.T) {
36 | // upload
37 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
38 | err = O.Upload(objectSVG, objectSVG,headerSVG)
39 | if err != nil {
40 | t.Error(err)
41 | }
42 | err = O.Upload(objectSVGGzip, objectSVGGzip, headerGzip, headerSVG)
43 | if err != nil {
44 | t.Error(err)
45 | }
46 | t.Log("=====IsExist=====")
47 | t.Log(objectSVG, "is exist?(Y):", O.IsExist(objectSVG) == nil)
48 | t.Log(objectNotExist, "is exist?(N):", O.IsExist(objectNotExist) == nil)
49 | t.Log("=====Lists=====")
50 | if files, err := O.Lists(objectPrefix); err != nil {
51 | t.Error(err)
52 | } else {
53 | t.Log(fmt.Sprintf("%+v", files))
54 | }
55 | t.Log("=====GetInfo=====")
56 | if info, err := O.GetInfo(objectSVG); err != nil {
57 | t.Error(err.Error())
58 | } else {
59 | t.Log(fmt.Sprintf("%+v", info))
60 | }
61 | t.Log("=====Download=====")
62 | if err := O.Download(objectSVG, objectDownload); err != nil {
63 | t.Error(err)
64 | } else {
65 | t.Log("download success")
66 | b, _ := ioutil.ReadFile(objectDownload)
67 | t.Log("Content:", string(b))
68 | os.Remove(objectDownload)
69 | }
70 | t.Log("====GetSignURL====")
71 | t.Log(O.GetSignURL(objectSVG, 1200))
72 | t.Log(O.GetSignURL(objectSVGGzip, 1200))
73 | t.Log("========Finished========")
74 | }
75 |
76 | func TestOSS_Delete(t *testing.T) {
77 | if err := O.Delete(objectSVG, objectSVGGzip); err != nil {
78 | t.Error(err)
79 | } else {
80 | t.Log("delete success")
81 | }
82 |
83 | if files, err := O.Lists(objectPrefix); err != nil {
84 | t.Error(err)
85 | } else {
86 | t.Log(fmt.Sprintf("%+v", files))
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/cos_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "testing"
8 |
9 | "github.com/astaxie/beego"
10 | )
11 |
12 | var (
13 | Cos *COS
14 | objectCOS = "cos.go"
15 | )
16 |
17 | func init() {
18 | var err error
19 | secretId := beego.AppConfig.String("cos::accessKey")
20 | secretKey := beego.AppConfig.String("cos::secretKey")
21 | bucket := beego.AppConfig.String("cos::bucket")
22 | appId := beego.AppConfig.String("cos::appId")
23 | region := beego.AppConfig.String("cos::region")
24 | domain := beego.AppConfig.String("cos::domain")
25 | Cos, err = NewCOS(secretId, secretKey, bucket, appId, region, domain)
26 | if err != nil {
27 | panic(err)
28 | }
29 | }
30 |
31 | func TestCOS(t *testing.T) {
32 | // upload
33 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
34 | err = Cos.Upload(objectSVG, objectSVG,headerSVG)
35 | if err != nil {
36 | t.Error(err)
37 | }
38 | err = Cos.Upload(objectSVGGzip, objectSVGGzip, headerGzip,headerSVG)
39 | if err != nil {
40 | t.Error(err)
41 | }
42 | t.Log("=====IsExist=====")
43 | t.Log(objectSVG, "is exist?(Y):", Cos.IsExist(objectSVG) == nil)
44 | t.Log(objectNotExist, "is exist?(N):", Cos.IsExist(objectNotExist) == nil)
45 | t.Log("=====Lists=====")
46 | if files, err := Cos.Lists(objectPrefix); err != nil {
47 | t.Error(err)
48 | } else {
49 | t.Log(fmt.Sprintf("%+v", files))
50 | }
51 | t.Log("=====GetInfo=====")
52 | if info, err := Cos.GetInfo(objectSVG); err != nil {
53 | t.Error(err.Error())
54 | } else {
55 | t.Log(fmt.Sprintf("%+v", info))
56 | }
57 | t.Log("=====Download=====")
58 | if err := Cos.Download(objectSVG, objectDownload); err != nil {
59 | t.Error(err)
60 | } else {
61 | t.Log("download success")
62 | b, _ := ioutil.ReadFile(objectDownload)
63 | t.Log("Content:", string(b))
64 | os.Remove(objectDownload)
65 | }
66 | t.Log("====GetSignURL====")
67 | t.Log(Cos.GetSignURL(objectSVG, 120))
68 | t.Log(Cos.GetSignURL(objectSVGGzip, 120))
69 | t.Log("========Finished========")
70 | }
71 |
72 | func TestCOS_Delete(t *testing.T) {
73 | if err := Cos.Delete(objectSVG, objectSVGGzip); err != nil {
74 | t.Error(err)
75 | } else {
76 | t.Log("delete success")
77 | }
78 |
79 | if files, err := Cos.Lists(objectPrefix); err != nil {
80 | t.Error(err)
81 | } else {
82 | t.Log(fmt.Sprintf("%+v", files))
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/minio_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/astaxie/beego"
11 | )
12 |
13 | var (
14 | Minio *MinIO
15 | objectMinio = "minio.go"
16 | )
17 |
18 | func init() {
19 | key := beego.AppConfig.String("minio::accessKey")
20 | secret := beego.AppConfig.String("minio::secretKey")
21 | bucket := beego.AppConfig.String("minio::bucket")
22 | domain := strings.ToLower(beego.AppConfig.String("minio::domain"))
23 | endpoint := strings.ToLower(beego.AppConfig.String("minio::endpoint"))
24 | Minio, err = NewMinIO(key, secret, bucket, endpoint, domain)
25 | if err != nil {
26 | panic(err)
27 | }
28 | }
29 | func TestMinIO(t *testing.T) {
30 | // upload
31 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
32 | err = Minio.Upload(objectSVG, objectSVG,headerSVG)
33 | if err != nil {
34 | t.Error(err)
35 | }
36 | err = Minio.Upload(objectSVGGzip, objectSVGGzip, headerGzip,headerSVG)
37 | if err != nil {
38 | t.Error(err)
39 | }
40 | t.Log("=====IsExist=====")
41 | t.Log(objectSVG, "is exist?(Y):", Minio.IsExist(objectSVG) == nil)
42 | t.Log(objectNotExist, "is exist?(N):", Minio.IsExist(objectNotExist) == nil)
43 | t.Log("=====Lists=====")
44 | if files, err := Minio.Lists(objectPrefix); err != nil {
45 | t.Error(err)
46 | } else {
47 | t.Log(fmt.Sprintf("%+v", files))
48 | }
49 | t.Log("=====GetInfo=====")
50 | if info, err := Minio.GetInfo(objectSVG); err != nil {
51 | t.Error(err.Error())
52 | } else {
53 | t.Log(fmt.Sprintf("%+v", info))
54 | }
55 | t.Log("=====Download=====")
56 | if err := Minio.Download(objectSVG, objectDownload); err != nil {
57 | t.Error(err)
58 | } else {
59 | t.Log("download success")
60 | b, _ := ioutil.ReadFile(objectDownload)
61 | t.Log("Content:", string(b))
62 | os.Remove(objectDownload)
63 | }
64 | t.Log("====GetSignURL====")
65 | t.Log(Minio.GetSignURL(objectSVG, 120))
66 | t.Log(Minio.GetSignURL(objectSVGGzip, 120))
67 | t.Log("========Finished========")
68 | }
69 |
70 | func TestMinIO_Delete(t *testing.T) {
71 | if err := Minio.Delete(objectSVG, objectSVGGzip); err != nil {
72 | t.Error(err)
73 | } else {
74 | t.Log("delete success")
75 | }
76 |
77 | if files, err := Minio.Lists(objectPrefix); err != nil {
78 | t.Error(err)
79 | } else {
80 | t.Log(fmt.Sprintf("%+v", files))
81 | }
82 | }
--------------------------------------------------------------------------------
/bos_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/astaxie/beego"
11 | )
12 |
13 | var (
14 | objectBOS = "bos.go"
15 | Bos *BOS
16 | err error
17 | )
18 |
19 | func init() {
20 |
21 | key := beego.AppConfig.String("bos::accessKey")
22 | secret := beego.AppConfig.String("bos::secretKey")
23 | endpoint := beego.AppConfig.String("bos::endpoint")
24 | bucket := beego.AppConfig.String("bos::bucket")
25 | domain := strings.ToLower(beego.AppConfig.String("bos::domain"))
26 |
27 | Bos, err = NewBOS(key, secret, bucket, endpoint, domain)
28 | if err != nil {
29 | panic(err)
30 | }
31 | }
32 |
33 | func TestBOS(t *testing.T) {
34 | // upload
35 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
36 | err = Bos.Upload(objectSVG, objectSVG, headerSVG)
37 | if err != nil {
38 | t.Error(err)
39 | }
40 | err = Bos.Upload(objectSVGGzip, objectSVGGzip, headerGzip, headerSVG)
41 | if err != nil {
42 | t.Error(err)
43 | }
44 | t.Log("=====IsExist=====")
45 | t.Log(objectSVG, "is exist?(Y):", Bos.IsExist(objectSVG) == nil)
46 | t.Log(objectNotExist, "is exist?(N):", Bos.IsExist(objectNotExist) == nil)
47 | t.Log("=====Lists=====")
48 | if files, err := Bos.Lists(objectPrefix); err != nil {
49 | t.Error(err)
50 | } else {
51 | if len(files) == 0 {
52 | t.Error("获取列表数据失败")
53 | }
54 | t.Log(fmt.Sprintf("%+v", files))
55 | }
56 | t.Log("=====GetInfo=====")
57 | if info, err := Bos.GetInfo(objectSVG); err != nil {
58 | t.Error(err.Error())
59 | } else {
60 | t.Log(fmt.Sprintf("%+v", info))
61 | }
62 | t.Log("=====Download=====")
63 | if err := Bos.Download(objectSVG, objectDownload); err != nil {
64 | t.Error(err)
65 | } else {
66 | t.Log("download success")
67 | b, _ := ioutil.ReadFile(objectDownload)
68 | t.Log("Content:", string(b))
69 | os.Remove(objectDownload)
70 | }
71 | t.Log("====GetSignURL====")
72 | t.Log(Bos.GetSignURL(objectSVG, 120))
73 | t.Log(Bos.GetSignURL(objectSVGGzip, 120))
74 | t.Log("========Finished========")
75 | }
76 |
77 | func TestBOS_GetSignURL(t *testing.T) {
78 | t.Log(Bos.GetSignURL(objectSVG, 3600))
79 | }
80 |
81 | func TestBOS_Delete(t *testing.T) {
82 | if err := Bos.Delete(objectSVG, objectSVGGzip); err != nil {
83 | t.Error(err)
84 | } else {
85 | t.Log("delete success")
86 | }
87 |
88 | if files, err := Bos.Lists(objectPrefix); err != nil {
89 | t.Error(err)
90 | } else {
91 | t.Log(fmt.Sprintf("%+v", files))
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### macOS template
3 | # General
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 | # Thumbnails
12 | ._*
13 | .idea
14 | # Files that might appear in the root of a volume
15 | .DocumentRevisions-V100
16 | .fseventsd
17 | .Spotlight-V100
18 | .TemporaryItems
19 | .Trashes
20 | .VolumeIcon.icns
21 | .com.apple.timemachine.donotpresent
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 | ### JetBrains template
30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
31 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
32 |
33 | # User-specific stuff
34 | .idea/**/workspace.xml
35 | .idea/**/tasks.xml
36 | .idea/**/usage.statistics.xml
37 | .idea/**/dictionaries
38 | .idea/**/shelf
39 |
40 | # Sensitive or high-churn files
41 | .idea/**/dataSources/
42 | .idea/**/dataSources.ids
43 | .idea/**/dataSources.local.xml
44 | .idea/**/sqlDataSources.xml
45 | .idea/**/dynamic.xml
46 | .idea/**/uiDesigner.xml
47 | .idea/**/dbnavigator.xml
48 |
49 | # Gradle
50 | .idea/**/gradle.xml
51 | .idea/**/libraries
52 |
53 | # Gradle and Maven with auto-import
54 | # When using Gradle or Maven with auto-import, you should exclude module files,
55 | # since they will be recreated, and may cause churn. Uncomment if using
56 | # auto-import.
57 | # .idea/modules.xml
58 | # .idea/*.iml
59 | # .idea/modules
60 |
61 | # CMake
62 | cmake-build-*/
63 |
64 | # Mongo Explorer plugin
65 | .idea/**/mongoSettings.xml
66 |
67 | # File-based project format
68 | *.iws
69 |
70 | # IntelliJ
71 | out/
72 |
73 | # mpeltonen/sbt-idea plugin
74 | .idea_modules/
75 |
76 | # JIRA plugin
77 | atlassian-ide-plugin.xml
78 |
79 | # Cursive Clojure plugin
80 | .idea/replstate.xml
81 |
82 | # Crashlytics plugin (for Android Studio and IntelliJ)
83 | com_crashlytics_export_strings.xml
84 | crashlytics.properties
85 | crashlytics-build.properties
86 | fabric.properties
87 |
88 | # Editor-based Rest Client
89 | .idea/httpRequests
90 | ### Go template
91 | # Binaries for programs and plugins
92 | *.exe
93 | *.exe~
94 | *.dll
95 | *.so
96 | *.dylib
97 |
98 | # Test binary, build with `go test -c`
99 | *.test
100 |
101 | # Output of the go coverage tool, specifically when used with LiteIDE
102 | *.out
103 |
104 |
105 | conf/app.conf
--------------------------------------------------------------------------------
/obs_test.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "obs"
8 | "os"
9 | "testing"
10 |
11 | "github.com/astaxie/beego"
12 | )
13 |
14 | var Obs *OBS
15 |
16 | func init() {
17 | accessKey := beego.AppConfig.String("obs::accessKey")
18 | secretKey := beego.AppConfig.String("obs::secretKey")
19 | bucket := beego.AppConfig.String("obs::bucket")
20 | endpoint := beego.AppConfig.String("obs::endpoint")
21 | domain := beego.AppConfig.String("obs::domain")
22 | Obs, err = NewOBS(accessKey, secretKey, bucket, endpoint, domain)
23 | if err != nil {
24 | panic(err)
25 | }
26 | }
27 |
28 | func TestOBS(t *testing.T) {
29 | // upload
30 | t.Log("=====Upload=====", objectSVG, objectSVGGzip)
31 | err = Obs.Upload(objectSVG, objectSVG)
32 | if err != nil {
33 | t.Error(err)
34 | }
35 | err = Obs.Upload(objectSVGGzip, objectSVGGzip, headerGzip, headerSVG)
36 | if err != nil {
37 | t.Error(err)
38 | }
39 | t.Log("=====IsExist=====")
40 | t.Log(objectSVG, "is exist?(Y):", Obs.IsExist(objectSVG) == nil)
41 | t.Log(objectNotExist, "is exist?(N):", Obs.IsExist(objectNotExist) == nil)
42 | t.Log("=====Lists=====")
43 | if files, err := Obs.Lists(objectPrefix); err != nil {
44 | t.Error(err)
45 | } else {
46 | t.Log(fmt.Sprintf("%+v", files))
47 | }
48 | t.Log("=====GetInfo=====")
49 | if info, err := Obs.GetInfo(objectSVG); err != nil {
50 | t.Error(err.Error())
51 | } else {
52 | t.Log(fmt.Sprintf("%+v", info))
53 | }
54 | t.Log("=====Download=====")
55 | if err := Obs.Download(objectSVG, objectDownload); err != nil {
56 | t.Error(err)
57 | } else {
58 | t.Log("download success")
59 | b, _ := ioutil.ReadFile(objectDownload)
60 | t.Log("Content:", string(b))
61 | os.Remove(objectDownload)
62 | }
63 | t.Log("====GetSignURL====")
64 | t.Log(Obs.GetSignURL(objectSVG, 1200))
65 | t.Log(Obs.GetSignURL(objectSVGGzip, 1200))
66 | t.Log("========Finished========")
67 | }
68 |
69 | func TestOBS_Upload(t *testing.T) {
70 | input := &obs.PutObjectInput{}
71 | input.Bucket = Obs.Bucket
72 | input.Key = "official.html"
73 | b, _ := ioutil.ReadFile(objectHtml)
74 | input.Body = bytes.NewBuffer(b)
75 |
76 | _, err := Obs.Client.PutObject(input)
77 | if err != nil {
78 | panic(err)
79 | }
80 | fmt.Printf("Create object:%s successfully!\n", input.Key)
81 | fmt.Println()
82 | fmt.Println(Obs.GetSignURL(input.Key, 3600))
83 | }
84 |
85 | func TestOBS_Delete(t *testing.T) {
86 | if err := Obs.Delete(objectSVG, objectSVGGzip, objectHtml, objectHtmlGzip); err != nil {
87 | t.Error(err)
88 | } else {
89 | t.Log("delete success")
90 | }
91 |
92 | if files, err := Obs.Lists(objectPrefix); err != nil {
93 | t.Error(err)
94 | } else {
95 | t.Log(fmt.Sprintf("%+v", files))
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CloudStore - 云储存集成
2 |
3 | 国内各大云存储服务接口集成,让云存储使用更方便简单。
4 |
5 | 目前集成的有:`阿里云OSS`,`百度云BOS`、`腾讯云COS`、`华为云OBS`、`七牛云`、`又拍云`、[Minio](https://www.bookstack.cn/books/MinioCookbookZH)
6 |
7 | ## 为什么要有这个项目?
8 |
9 | 为了一劳永逸...
10 |
11 | 为了变得更懒...
12 |
13 | 如果上传文件到各大云存储,都变成下面这样:
14 | ```
15 | clientBOS.Upload(tmpFile, saveFile) // 百度云
16 | clientCOS.Upload(tmpFile, saveFile) // 腾讯云
17 | clientMinio.Upload(tmpFile, saveFile) // Minio
18 | clientOBS.Upload(tmpFile, saveFile) // 华为云
19 | clientOSS.Upload(tmpFile, saveFile) // 阿里云
20 | clientUpYun.Upload(tmpFile, saveFile) // 又拍云
21 | clientQiniu.Upload(tmpFile, saveFile) // 七牛云
22 | ```
23 |
24 | 如果各大云存储删除文件对象,都变成下面这样:
25 | ```
26 | clientXXX.Delete(file1, file2, file3, ...)
27 | ```
28 |
29 | 不需要翻看各大云存储服务的一大堆文档,除了创建的客户端对象不一样之外,调用的方法和参数都一毛一样,会不会很爽?
30 |
31 |
32 |
33 | ## 目前初步实现的功能接口
34 |
35 | ```
36 | type CloudStore interface {
37 | Delete(objects ...string) (err error) // 删除文件
38 | GetSignURL(object string, expire int64) (link string, err error) // 文件访问签名
39 | IsExist(object string) (err error) // 判断文件是否存在
40 | Lists(prefix string) (files []File, err error) // 文件前缀,列出文件
41 | Upload(tmpFile string, saveFile string, headers ...map[string]string) (err error) // 上传文件
42 | Download(object string, savePath string) (err error) // 下载文件
43 | GetInfo(object string) (info File, err error) // 获取指定文件信息
44 | }
45 | ```
46 |
47 |
48 | ## 目前集成和实现的功能
49 |
50 | - [x] oss - 阿里云云存储 [SDK](https://github.com/aliyun/aliyun-oss-go-sdk) && [文档](https://www.bookstack.cn/books/aliyun-oss-go-sdk)
51 | - [x] cos - 腾讯云云存储 [SDK](https://github.com/tencentyun/cos-go-sdk-v5) && [文档](https://www.bookstack.cn/books/tencent-cos-go-sdk)
52 | - [x] bos - 百度云云存储 [SDK](https://github.com/baidubce/bce-sdk-go) && [文档](https://www.bookstack.cn/books/bos-go-sdk)
53 | - [x] qiniu - 七牛云存储 [SDK](https://github.com/qiniu/api.v7) && [文档](https://www.bookstack.cn/books/qiniu-go-sdk)
54 | - [x] upyun - 又拍云存储 [SDK](https://github.com/upyun/go-sdk) && [文档]()
55 | - [x] obs - 华为云云存储 [SDK](https://support.huaweicloud.com/devg-obs_go_sdk_doc_zh/zh-cn_topic_0142815182.html) && [文档](https://www.bookstack.cn/books/obs-go-sdk)
56 | - [x] minio [SDK](https://github.com/minio/minio-go) && [文档](https://www.bookstack.cn/books/MinioCookbookZH)
57 |
58 |
59 |
60 |
61 | TODO:
62 | - [x] 注意,domain 参数要处理一下,最后统一不带"/"
63 | - [x] 最后获取的签名链接,替换成绑定的域名
64 | - [x] timeout 时间要处理一下,因为一些非内网方式上传文件,在大文件的时候,5分钟或者10分钟都有可能会超时
65 | - [x] `Lists`方法在查询列表的时候,需要对prefix参数做下处理
66 |
67 | ## 注意
68 | 所有云存储的`endpoint`,在配置的时候都是不带 `http://`或者`https://`的
69 |
70 | ## DocHub 可用云存储
71 | - [x] 百度云 BOS,需要自行压缩svg文件为gzip
72 | - [x] 腾讯云 COS,需要自行压缩svg文件为gzip
73 | - [x] 阿里云 OSS,需要自行压缩svg文件为gzip
74 | - [x] Minio,需要自行压缩svg文件为gzip
75 | - [x] 七牛云存储,在上传svg的时候不需要压缩,svg访问的时候,云存储自行压缩了
76 | - [x] 又拍云,在上传svg的时候不需要压缩,svg访问的时候,云存储自行压缩了
77 | - [x] 华为云 OBS,在上传svg的时候不需要压缩,svg访问的时候,云存储自行压缩了
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/test_data/helloworld.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 | Hello World!==1
9 | Hello World!==2
10 | Hello World!==3
11 | Hello World!==4
12 | Hello World!==5
13 | Hello World!==6
14 | Hello World!==7
15 | Hello World!==8
16 | Hello World!==9
17 | Hello World!==1
18 | Hello World!==2
19 | Hello World!==3
20 | Hello World!==4
21 | Hello World!==5
22 | Hello World!==6
23 | Hello World!==7
24 | Hello World!==8
25 | Hello World!==9
26 | Hello World!==1
27 | Hello World!==2
28 | Hello World!==3
29 | Hello World!==4
30 | Hello World!==5
31 | Hello World!==6
32 | Hello World!==7
33 | Hello World!==8
34 | Hello World!==9
35 | Hello World!==1
36 | Hello World!==2
37 | Hello World!==3
38 | Hello World!==4
39 | Hello World!==5
40 | Hello World!==6
41 | Hello World!==7
42 | Hello World!==8
43 | Hello World!==9
44 | Hello World!==1
45 | Hello World!==2
46 | Hello World!==3
47 | Hello World!==4
48 | Hello World!==5
49 | Hello World!==6
50 | Hello World!==7
51 | Hello World!==8
52 | Hello World!==9
53 | Hello World!==1
54 | Hello World!==2
55 | Hello World!==3
56 | Hello World!==4
57 | Hello World!==5
58 | Hello World!==6
59 | Hello World!==7
60 | Hello World!==8
61 | Hello World!==9
62 | Hello World!==1
63 | Hello World!==2
64 | Hello World!==3
65 | Hello World!==4
66 | Hello World!==5
67 | Hello World!==6
68 | Hello World!==7
69 | Hello World!==8
70 | Hello World!==9
71 | Hello World!==1
72 | Hello World!==2
73 | Hello World!==3
74 | Hello World!==4
75 | Hello World!==5
76 | Hello World!==6
77 | Hello World!==7
78 | Hello World!==8
79 | Hello World!==9
80 | Hello World!==1
81 | Hello World!==2
82 | Hello World!==3
83 | Hello World!==4
84 | Hello World!==5
85 | Hello World!==6
86 | Hello World!==7
87 | Hello World!==8
88 | Hello World!==9
89 | Hello World!==1
90 | Hello World!==2
91 | Hello World!==3
92 | Hello World!==4
93 | Hello World!==5
94 | Hello World!==6
95 | Hello World!==7
96 | Hello World!==8
97 | Hello World!==9
98 | Hello World!==1
99 | Hello World!==2
100 | Hello World!==3
101 | Hello World!==4
102 | Hello World!==5
103 | Hello World!==6
104 | Hello World!==7
105 | Hello World!==8
106 | Hello World!==9
107 |
108 |
--------------------------------------------------------------------------------
/upyun.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | "time"
10 |
11 | "github.com/upyun/go-sdk/upyun"
12 | )
13 |
14 | type UpYun struct {
15 | Bucket string
16 | Operator string
17 | Password string
18 | Domain string
19 | Client *upyun.UpYun
20 | secret string
21 | }
22 |
23 | func NewUpYun(bucket, operator, password, domain, secret string) *UpYun {
24 | if !strings.HasPrefix(domain, "http://") && !strings.HasPrefix(domain, "https://") {
25 | domain = "http://" + domain
26 | }
27 | domain = strings.TrimRight(domain, "/")
28 | client := upyun.NewUpYun(&upyun.UpYunConfig{
29 | Bucket: bucket,
30 | Operator: operator,
31 | Password: password,
32 | })
33 | return &UpYun{
34 | Bucket: bucket,
35 | Operator: operator,
36 | Password: password,
37 | Domain: domain,
38 | Client: client,
39 | secret: secret,
40 | }
41 | }
42 |
43 | func (u *UpYun) IsExist(object string) (err error) {
44 | _, err = u.Client.GetInfo(objectAbs(object))
45 | return
46 | }
47 |
48 | func (u *UpYun) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
49 | _, err = os.Stat(tmpFile)
50 | h := make(map[string]string)
51 | if err != nil {
52 | return
53 | }
54 | for _, header := range headers {
55 | for k, v := range header {
56 | h[k] = v
57 | }
58 | }
59 | err = u.Client.Put(&upyun.PutObjectConfig{
60 | Path: objectAbs(saveFile),
61 | LocalPath: tmpFile,
62 | Headers: h,
63 | })
64 | return
65 | }
66 |
67 | func (u *UpYun) Delete(objects ...string) (err error) {
68 | var errs []string
69 | for _, object := range objects {
70 | err = u.Client.Delete(&upyun.DeleteObjectConfig{
71 | Path: objectAbs(object),
72 | })
73 | if err != nil {
74 | errs = append(errs, err.Error())
75 | }
76 | }
77 | if len(errs) > 0 {
78 | err = errors.New(strings.Join(errs, "; "))
79 | }
80 | return
81 | }
82 |
83 | // https://help.upyun.com/knowledge-base/cdn-token-limite/
84 | func (u *UpYun) GetSignURL(object string, expire int64) (link string, err error) {
85 | path := objectAbs(object)
86 | if expire <= 0 {
87 | return u.Domain + path, nil
88 | }
89 | endTime := time.Now().Unix() + expire
90 | sign := MD5Crypt(fmt.Sprintf("%v&%v&%v", u.secret, endTime, path))
91 | sign = strings.Join(strings.Split(sign, "")[12:20], "") + fmt.Sprint(endTime)
92 | return u.Domain + path + "?_upt=" + sign, nil
93 | }
94 |
95 | func (u *UpYun) Lists(prefix string) (files []File, err error) {
96 | chans := make(chan *upyun.FileInfo, 1000)
97 | prefix = objectRel(prefix)
98 | u.Client.List(&upyun.GetObjectsConfig{
99 | Path: prefix,
100 | ObjectsChan: chans,
101 | })
102 | var file File
103 | for obj := range chans {
104 | file = File{
105 | ModTime: obj.Time,
106 | Size: obj.Size,
107 | IsDir: obj.IsDir,
108 | Header: obj.Meta, // 注意:这里获取不到文件的header
109 | Name: filepath.Join(prefix, objectRel(obj.Name)),
110 | }
111 | files = append(files, file)
112 | }
113 | return
114 | }
115 |
116 | func (u *UpYun) Download(object string, savePath string) (err error) {
117 | _, err = u.Client.Get(&upyun.GetObjectConfig{
118 | Path: objectAbs(object),
119 | LocalPath: savePath,
120 | })
121 | return
122 | }
123 |
124 | func (u *UpYun) GetInfo(object string) (info File, err error) {
125 | var fileInfo *upyun.FileInfo
126 | fileInfo, err = u.Client.GetInfo(objectAbs(object))
127 | if err != nil {
128 | return
129 | }
130 | info = File{
131 | ModTime: fileInfo.Time,
132 | Name: objectRel(fileInfo.Name),
133 | Size: fileInfo.Size,
134 | IsDir: fileInfo.IsDir,
135 | Header: fileInfo.Meta,
136 | }
137 | return
138 | }
139 |
--------------------------------------------------------------------------------
/bos.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/url"
7 | "strings"
8 | "time"
9 |
10 | "github.com/baidubce/bce-sdk-go/services/bos"
11 | "github.com/baidubce/bce-sdk-go/services/bos/api"
12 | )
13 |
14 | type BOS struct {
15 | AccessKey string
16 | SecretKey string
17 | Bucket string
18 | Endpoint string
19 | Domain string
20 | Client *bos.Client
21 | }
22 |
23 | // new bos
24 | func NewBOS(accessKey, secretKey, bucket, endpoint, domain string) (b *BOS, err error) {
25 | b = &BOS{
26 | AccessKey: accessKey,
27 | SecretKey: secretKey,
28 | Bucket: bucket,
29 | Endpoint: endpoint,
30 | }
31 | if domain == "" {
32 | domain = "https://" + bucket + "." + endpoint
33 | }
34 | b.Domain = strings.TrimRight(domain, "/")
35 | b.Client, err = bos.NewClient(accessKey, secretKey, endpoint)
36 | return
37 | }
38 |
39 | func (b *BOS) IsExist(object string) (err error) {
40 | _, err = b.GetInfo(object)
41 | return
42 | }
43 |
44 | func (b *BOS) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
45 | var args = &api.PutObjectArgs{
46 | UserMeta: make(map[string]string),
47 | }
48 | for _, header := range headers {
49 | for k, v := range header {
50 | switch strings.ToLower(k) {
51 | case "content-disposition":
52 | args.ContentDisposition = v
53 | case "content-type":
54 | args.ContentType = v
55 | //case "content-encoding":
56 | // args.ContentEncoding = v
57 | default:
58 | args.UserMeta[k] = v
59 | }
60 | }
61 | }
62 | _, err = b.Client.PutObjectFromFile(b.Bucket, objectRel(saveFile), tmpFile, args)
63 | return
64 | }
65 |
66 | func (b *BOS) Delete(objects ...string) (err error) {
67 | if len(objects) == 0 {
68 | return
69 | }
70 | for idx, object := range objects {
71 | objects[idx] = objectRel(object)
72 | }
73 | res, _ := b.Client.DeleteMultipleObjectsFromKeyList(b.Bucket, objects)
74 | if res != nil && len(res.Errors) > 0 {
75 | err = fmt.Errorf("%+v", res)
76 | }
77 | return
78 | }
79 |
80 | func (b *BOS) GetSignURL(object string, expire int64) (link string, err error) {
81 | if expire <= 0 {
82 | link = b.Domain + objectAbs(object)
83 | } else {
84 | link = b.Client.BasicGeneratePresignedUrl(b.Bucket, objectRel(object), int(expire))
85 | if !strings.HasPrefix(link, b.Domain) {
86 | if u, errU := url.Parse(link); errU == nil {
87 | link = b.Domain + u.RequestURI()
88 | }
89 | }
90 | }
91 | return
92 | }
93 |
94 | func (b *BOS) Download(object string, savePath string) (err error) {
95 | err = b.Client.DownloadSuperFile(b.Bucket, objectRel(object), savePath)
96 | return
97 | }
98 |
99 | func (b *BOS) GetInfo(object string) (info File, err error) {
100 | var resp *api.GetObjectMetaResult
101 | resp, err = b.Client.GetObjectMeta(b.Bucket, objectRel(object))
102 | if err != nil {
103 | return
104 | }
105 | info = File{
106 | Name: objectRel(object),
107 | Size: resp.ContentLength,
108 | IsDir: resp.ContentLength == 0,
109 | Header: resp.UserMeta,
110 | }
111 | info.ModTime, _ = time.Parse(http.TimeFormat, resp.LastModified)
112 | return
113 | }
114 |
115 | func (b *BOS) Lists(prefix string) (files []File, err error) {
116 | var resp *api.ListObjectsResult
117 | args := &api.ListObjectsArgs{
118 | Prefix: objectRel(prefix),
119 | MaxKeys: 1000,
120 | }
121 | resp, err = b.Client.ListObjects(b.Bucket, args)
122 | if err != nil {
123 | return
124 | }
125 |
126 | for _, object := range resp.Contents {
127 | file := File{
128 | Size: int64(object.Size),
129 | Name: objectRel(object.Key),
130 | IsDir: object.Size == 0,
131 | }
132 | file.ModTime, _ = time.Parse(http.TimeFormat, object.LastModified)
133 | files = append(files, file)
134 | }
135 | return
136 | }
137 |
--------------------------------------------------------------------------------
/cos.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "net/http"
8 | "net/url"
9 | "os"
10 | "strconv"
11 | "strings"
12 | "time"
13 |
14 | "github.com/tencentyun/cos-go-sdk-v5"
15 | )
16 |
17 | type COS struct {
18 | AccessKey string
19 | SecretKey string
20 | Bucket string
21 | AppID string
22 | Region string
23 | Domain string
24 | Client *cos.Client
25 | }
26 |
27 | func NewCOS(accessKey, secretKey, bucket, appId, region, domain string) (c *COS, err error) {
28 | c = &COS{
29 | AccessKey: accessKey,
30 | SecretKey: secretKey,
31 | Bucket: bucket,
32 | AppID: appId,
33 | Region: region,
34 | }
35 | u, _ := url.Parse(fmt.Sprintf("https://%v-%v.cos.%v.myqcloud.com", bucket, appId, region))
36 | if domain == "" {
37 | domain = u.String()
38 | }
39 | c.Domain = strings.TrimRight(domain, "/ ")
40 | c.Client = cos.NewClient(
41 | &cos.BaseURL{BucketURL: u},
42 | &http.Client{
43 | Timeout: 1800 * time.Second,
44 | Transport: &cos.AuthorizationTransport{
45 | SecretID: accessKey,
46 | SecretKey: secretKey,
47 | },
48 | })
49 | return
50 | }
51 |
52 | func (c *COS) IsExist(object string) (err error) {
53 | _, err = c.GetInfo(object)
54 | return
55 | }
56 |
57 | func (c *COS) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
58 | var reader *os.File
59 | reader, err = os.Open(tmpFile)
60 | if err != nil {
61 | return
62 | }
63 | defer reader.Close()
64 | objHeader := &cos.ObjectPutHeaderOptions{}
65 | for _, header := range headers {
66 | for k, v := range header {
67 | switch strings.ToLower(k) {
68 | case "content-encoding":
69 | objHeader.ContentEncoding = v
70 | case "content-type":
71 | objHeader.ContentType = v
72 | case "content-disposition":
73 | objHeader.ContentDisposition = v
74 | }
75 | }
76 | }
77 | opt := &cos.ObjectPutOptions{ObjectPutHeaderOptions: objHeader}
78 | _, err = c.Client.Object.Put(context.Background(), objectRel(saveFile), reader, opt)
79 | return
80 | }
81 |
82 | func (c *COS) Delete(objects ...string) (err error) {
83 | var errs []string
84 | for _, object := range objects {
85 | _, err = c.Client.Object.Delete(context.Background(), objectRel(object))
86 | if err != nil {
87 | errs = append(errs, err.Error())
88 | }
89 | }
90 | if len(errs) > 0 {
91 | err = errors.New(strings.Join(errs, "; "))
92 | }
93 | return
94 | }
95 |
96 | func (c *COS) GetSignURL(object string, expire int64) (link string, err error) {
97 | if expire <= 0 {
98 | link = c.Domain + objectAbs(object)
99 | return
100 | }
101 |
102 | var u *url.URL
103 | exp := time.Duration(expire) * time.Second
104 | u, err = c.Client.Object.GetPresignedURL(context.Background(),
105 | http.MethodGet, objectRel(object),
106 | c.AccessKey, c.SecretKey,
107 | exp, nil)
108 | if err != nil {
109 | return
110 | }
111 | link = u.String()
112 | if !strings.HasPrefix(link, c.Domain) {
113 | link = c.Domain + u.RequestURI()
114 | }
115 | return
116 | }
117 |
118 | func (c *COS) Download(object string, savePath string) (err error) {
119 | _, err = c.Client.Object.GetToFile(context.Background(), objectRel(object), savePath, nil)
120 | return
121 | }
122 |
123 | func (c *COS) GetInfo(object string) (info File, err error) {
124 | var resp *cos.Response
125 | path := objectRel(object)
126 | resp, err = c.Client.Object.Get(context.Background(), path, nil)
127 | if err != nil {
128 | return
129 | }
130 | defer resp.Body.Close()
131 | header := make(map[string]string)
132 | for k, _ := range resp.Header {
133 | header[k] = resp.Header.Get(k)
134 | }
135 | info = File{
136 | Header: header,
137 | Name: path,
138 | }
139 | info.ModTime, _ = time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
140 | info.Size, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
141 | info.IsDir = info.Size == 0
142 | return
143 | }
144 |
145 | func (c *COS) Lists(prefix string) (files []File, err error) {
146 | // TODO: 腾讯云的SDK中暂时没开放这个功能
147 | return
148 | }
149 |
--------------------------------------------------------------------------------
/oss.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "net/url"
7 | "strconv"
8 | "strings"
9 | "time"
10 |
11 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
12 | )
13 |
14 | type OSS struct {
15 | AccessKey string
16 | SecretKey string
17 | Endpoint string
18 | Bucket string
19 | Domain string
20 | Client *oss.Bucket
21 | }
22 |
23 | // New OSS
24 | func NewOSS(accessKey, secretKey, endpoint, bucket, domain string) (o *OSS, err error) {
25 | var client *oss.Client
26 | if domain == "" {
27 | domain = "https://" + bucket + "." + endpoint
28 | }
29 | domain = strings.TrimRight(domain, "/ ")
30 | o = &OSS{
31 | AccessKey: accessKey,
32 | SecretKey: secretKey,
33 | Endpoint: endpoint,
34 | Bucket: bucket,
35 | Domain: domain,
36 | }
37 | client, err = oss.New(endpoint, accessKey, secretKey)
38 | if err != nil {
39 | return
40 | }
41 | o.Client, err = client.Bucket(bucket)
42 | return
43 | }
44 |
45 | func (o *OSS) IsExist(object string) (err error) {
46 | var b bool
47 | b, err = o.Client.IsObjectExist(objectRel(object))
48 | if err != nil {
49 | return
50 | }
51 | if !b {
52 | return errors.New("file is not exist")
53 | }
54 | return
55 | }
56 |
57 | func (o *OSS) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
58 | var opts []oss.Option
59 | for _, header := range headers {
60 | for k, v := range header {
61 | switch strings.ToLower(k) {
62 | case "content-type":
63 | opts = append(opts, oss.ContentType(v))
64 | case "content-encoding":
65 | opts = append(opts, oss.ContentEncoding(v))
66 | case "content-disposition":
67 | opts = append(opts, oss.ContentDisposition(v))
68 | // TODO: more
69 | }
70 | }
71 | }
72 | err = o.Client.PutObjectFromFile(strings.TrimLeft(saveFile, "./"), tmpFile, opts...)
73 | return
74 | }
75 |
76 | func (o *OSS) Delete(objects ...string) (err error) {
77 | _, err = o.Client.DeleteObjects(objects)
78 | return
79 | }
80 |
81 | func (o *OSS) GetSignURL(object string, expire int64) (link string, err error) {
82 | path := objectRel(object)
83 | if expire <= 0 {
84 | return o.Domain + "/" + path, nil
85 | }
86 | link, err = o.Client.SignURL(path, http.MethodGet, expire)
87 | if err != nil {
88 | return
89 | }
90 | if !strings.HasPrefix(link, o.Domain) {
91 | if u, errU := url.Parse(link); errU == nil {
92 | link = o.Domain + u.RequestURI()
93 | }
94 | }
95 |
96 | return
97 | }
98 |
99 | func (o *OSS) Download(object string, savePath string) (err error) {
100 | err = o.Client.DownloadFile(objectRel(object), savePath, 1048576)
101 | return
102 | }
103 |
104 | func (o *OSS) GetInfo(object string) (info File, err error) {
105 | // https://help.aliyun.com/document_detail/31859.html?spm=a2c4g.11186623.2.10.713d1592IKig7s#concept-lkf-swy-5db
106 | //Cache-Control 指定该 Object 被下载时的网页的缓存行为
107 | //Content-Disposition 指定该 Object 被下载时的名称
108 | //Content-Encoding 指定该 Object 被下载时的内容编码格式
109 | //Content-Language 指定该 Object 被下载时的内容语言编码
110 | //Expires 过期时间
111 | //Content-Length 该 Object 大小
112 | //Content-Type 该 Object 文件类型
113 | //Last-Modified 最近修改时间
114 |
115 | var header http.Header
116 |
117 | path := objectRel(object)
118 | header, err = o.Client.GetObjectMeta(path)
119 | if err != nil {
120 | return
121 | }
122 |
123 | headerMap := make(map[string]string)
124 |
125 | for k, _ := range header {
126 | headerMap[k] = header.Get(k)
127 | }
128 |
129 | info.Header = headerMap
130 | info.Size, _ = strconv.ParseInt(header.Get("Content-Length"), 10, 64)
131 | info.ModTime, _ = time.Parse(http.TimeFormat, header.Get("Last-Modified"))
132 | info.Name = path
133 | info.IsDir = false
134 | return
135 | }
136 |
137 | func (o *OSS) Lists(prefix string) (files []File, err error) {
138 | prefix = objectRel(prefix)
139 |
140 | var res oss.ListObjectsResult
141 |
142 | res, err = o.Client.ListObjects(oss.Prefix(prefix))
143 | if err != nil {
144 | return
145 | }
146 | for _, object := range res.Objects {
147 | files = append(files, File{
148 | ModTime: object.LastModified,
149 | Name: object.Key,
150 | Size: object.Size,
151 | IsDir: object.Size == 0,
152 | Header: map[string]string{},
153 | })
154 | }
155 | return
156 | }
157 |
--------------------------------------------------------------------------------
/obs.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "net/url"
9 | "os"
10 | "strings"
11 |
12 | "github.com/TruthHun/CloudStore/obs"
13 | )
14 |
15 | type OBS struct {
16 | AccessKey string
17 | SecretKey string
18 | Bucket string
19 | Endpoint string
20 | Domain string
21 | Client *obs.ObsClient
22 | }
23 |
24 | func NewOBS(accessKey, secretKey, bucket, endpoint, domain string) (o *OBS, err error) {
25 | o = &OBS{
26 | AccessKey: accessKey,
27 | SecretKey: secretKey,
28 | Endpoint: endpoint,
29 | Bucket: bucket,
30 | }
31 |
32 | if domain == "" {
33 | domain = fmt.Sprintf("https://%v.%v", bucket, endpoint)
34 | }
35 | o.Domain = strings.TrimRight(domain, "/")
36 | o.Client, err = obs.New(accessKey, secretKey, endpoint)
37 | return
38 | }
39 |
40 | func (o *OBS) IsExist(object string) (err error) {
41 | _, err = o.GetInfo(object)
42 | return
43 | }
44 |
45 | func (o *OBS) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
46 | var p []byte
47 | p, err = ioutil.ReadFile(tmpFile)
48 | if err != nil {
49 | return
50 | }
51 |
52 | input := &obs.PutObjectInput{}
53 | input.Bucket = o.Bucket
54 | input.Key = objectRel(saveFile)
55 | input.Metadata = make(map[string]string)
56 | input.Body = bytes.NewBuffer(p)
57 |
58 | for _, header := range headers {
59 | for k, v := range header {
60 | switch strings.ToLower(k) {
61 | case "content-type":
62 | input.ContentType = v
63 | case "content-encoding":
64 | input.ContentEncoding = v
65 | case "content-disposition":
66 | input.ContentDisposition = v
67 | default:
68 | input.Metadata[k] = v
69 | }
70 | }
71 | }
72 | _, err = o.Client.PutObject(input)
73 | return
74 | }
75 |
76 | func (o *OBS) Delete(objects ...string) (err error) {
77 | if len(objects) <= 0 {
78 | return
79 | }
80 | var objs []obs.ObjectToDelete
81 | for _, object := range objects {
82 | objs = append(objs, obs.ObjectToDelete{
83 | Key: objectRel(object),
84 | })
85 | }
86 | input := &obs.DeleteObjectsInput{
87 | Bucket: o.Bucket,
88 | Objects: objs,
89 | }
90 | _, err = o.Client.DeleteObjects(input)
91 | return
92 | }
93 |
94 | func (o *OBS) GetSignURL(object string, expire int64) (link string, err error) {
95 | if expire <= 0 {
96 | link = o.Domain + objectAbs(object)
97 | return
98 | }
99 | input := &obs.CreateSignedUrlInput{
100 | Method: http.MethodGet,
101 | Bucket: o.Bucket,
102 | Key: objectRel(object),
103 | Expires: int(expire),
104 | }
105 | output := &obs.CreateSignedUrlOutput{}
106 | output, err = o.Client.CreateSignedUrl(input)
107 | if err != nil {
108 | return
109 | }
110 | link = output.SignedUrl
111 | if !strings.HasPrefix(link, o.Domain) {
112 | if u, errU := url.Parse(link); errU == nil {
113 | link = o.Domain + u.RequestURI()
114 | }
115 | }
116 | return
117 | }
118 |
119 | func (o *OBS) Download(object string, savePath string) (err error) {
120 | input := &obs.GetObjectInput{}
121 | input.Key = objectRel(object)
122 | input.Bucket = o.Bucket
123 |
124 | output := &obs.GetObjectOutput{}
125 | output, err = o.Client.GetObject(input)
126 | if err != nil {
127 | return
128 | }
129 | defer output.Body.Close()
130 |
131 | var b []byte
132 | b, err = ioutil.ReadAll(output.Body)
133 | if err != nil {
134 | return
135 | }
136 |
137 | return ioutil.WriteFile(savePath, b, os.ModePerm)
138 | }
139 |
140 | func (o *OBS) GetInfo(object string) (info File, err error) {
141 | input := &obs.GetObjectMetadataInput{
142 | Bucket: o.Bucket,
143 | Key: objectRel(object),
144 | }
145 | output := &obs.GetObjectMetadataOutput{}
146 | output, err = o.Client.GetObjectMetadata(input)
147 | if err != nil {
148 | return
149 | }
150 | info = File{
151 | Name: objectRel(object),
152 | Size: output.ContentLength,
153 | IsDir: output.ContentLength == 0,
154 | ModTime: output.LastModified,
155 | }
156 | return
157 | }
158 |
159 | func (o *OBS) Lists(prefix string) (files []File, err error) {
160 | prefix = objectRel(prefix)
161 | input := &obs.ListObjectsInput{}
162 | input.Prefix = prefix
163 | input.Bucket = o.Bucket
164 | output := &obs.ListObjectsOutput{}
165 | output, err = o.Client.ListObjects(input)
166 | if err != nil {
167 | return
168 | }
169 |
170 | for _, item := range output.Contents {
171 | files = append(files, File{
172 | ModTime: item.LastModified,
173 | Name: objectRel(item.Key),
174 | Size: item.Size,
175 | IsDir: item.Size == 0,
176 | })
177 | }
178 |
179 | return
180 | }
181 |
--------------------------------------------------------------------------------
/minio.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "net/url"
7 | "os"
8 | "strings"
9 | "time"
10 |
11 | "github.com/minio/minio-go"
12 | )
13 |
14 | type MinIO struct {
15 | AccessKey string
16 | SecretKey string
17 | Bucket string
18 | Endpoint string
19 | Domain string
20 | Client *minio.Client
21 | }
22 |
23 | func NewMinIO(accessKey, secretKey, bucket, endpoint, domain string) (m *MinIO, err error) {
24 | if domain == "" {
25 | domain = "http://" + endpoint
26 | }
27 | m = &MinIO{
28 | AccessKey: accessKey,
29 | SecretKey: secretKey,
30 | Bucket: bucket,
31 | Endpoint: endpoint,
32 | Domain: domain,
33 | }
34 | m.Client, err = minio.New(endpoint, accessKey, secretKey, false)
35 | m.Domain = strings.TrimRight(m.Domain, "/")
36 | return
37 | }
38 |
39 | func (m *MinIO) IsExist(object string) (err error) {
40 | _, err = m.GetInfo(object)
41 | return
42 | }
43 |
44 | func (m *MinIO) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
45 | var (
46 | fp *os.File
47 | info os.FileInfo
48 | )
49 | fp, err = os.Open(tmpFile)
50 | if err != nil {
51 | return
52 | }
53 | defer fp.Close()
54 |
55 | info, err = fp.Stat()
56 | if err != nil {
57 | return
58 | }
59 |
60 | opts := minio.PutObjectOptions{
61 | UserMetadata: make(map[string]string),
62 | }
63 |
64 | for _, header := range headers {
65 | for k, v := range header {
66 | switch strings.ToLower(k) {
67 | case "content-disposition":
68 | opts.ContentDisposition = v
69 | case "content-encoding":
70 | opts.ContentEncoding = v
71 | case "content-type":
72 | opts.ContentType = v
73 | default:
74 | opts.UserMetadata[k] = v
75 | }
76 | }
77 | }
78 |
79 | _, err = m.Client.PutObject(m.Bucket, objectRel(saveFile), fp, info.Size(), opts)
80 | return
81 | }
82 |
83 | func (m *MinIO) Delete(objects ...string) (err error) {
84 | if len(objects) == 0 {
85 | return
86 | }
87 |
88 | var errs []string
89 |
90 | objectsChan := make(chan string)
91 | go func() {
92 | defer close(objectsChan)
93 | for _, object := range objects {
94 | objectsChan <- objectRel(object)
95 | }
96 | }()
97 | for errRm := range m.Client.RemoveObjects(m.Bucket, objectsChan) {
98 | if errRm.Err != nil {
99 | errs = append(errs, errRm.Err.Error())
100 | }
101 | }
102 | if len(errs) > 0 {
103 | err = errors.New(strings.Join(errs, "; "))
104 | }
105 | return
106 | }
107 |
108 | func (m *MinIO) GetSignURL(object string, expire int64) (link string, err error) {
109 | if expire <= 0 {
110 | link = m.Domain + objectAbs(object)
111 | return
112 | }
113 | if expire > sevenDays {
114 | expire = sevenDays
115 | }
116 | exp := time.Duration(expire) * time.Second
117 | u := &url.URL{}
118 | u, err = m.Client.PresignedGetObject(m.Bucket, objectRel(object), exp, nil)
119 | if err != nil {
120 | return
121 | }
122 | link = u.String()
123 | if !strings.HasPrefix(link, m.Domain) {
124 | link = m.Domain + u.RequestURI()
125 | }
126 | return
127 | }
128 |
129 | func (m *MinIO) Download(object string, savePath string) (err error) {
130 | obj := &minio.Object{}
131 | obj, err = m.Client.GetObject(m.Bucket, objectRel(object), minio.GetObjectOptions{})
132 | if err != nil {
133 | return
134 | }
135 |
136 | _, err = obj.Stat()
137 | if err != nil {
138 | return
139 | }
140 |
141 | var fp *os.File
142 | fp, err = os.Create(savePath)
143 | if err != nil {
144 | return
145 | }
146 | defer fp.Close()
147 |
148 | _, err = io.Copy(fp, obj)
149 |
150 | return
151 | }
152 |
153 | func (m *MinIO) GetInfo(object string) (info File, err error) {
154 | var objInfo minio.ObjectInfo
155 | opts := minio.StatObjectOptions{}
156 | object = objectRel(object)
157 | objInfo, err = m.Client.StatObject(m.Bucket, object, opts)
158 | if err != nil {
159 | return
160 | }
161 | info = File{
162 | ModTime: objInfo.LastModified,
163 | Name: object,
164 | Size: objInfo.Size,
165 | IsDir: objInfo.Size == 0,
166 | Header: make(map[string]string),
167 | }
168 | for k, _ := range objInfo.Metadata {
169 | info.Header[k] = objInfo.Metadata.Get(k)
170 | }
171 | return
172 | }
173 |
174 | func (m *MinIO) Lists(prefix string) (files []File, err error) {
175 | prefix = objectRel(prefix)
176 | doneCh := make(chan struct{})
177 | defer close(doneCh)
178 | objects := m.Client.ListObjectsV2(m.Bucket, prefix, true, doneCh)
179 | for object := range objects {
180 | header := make(map[string]string)
181 | file := File{
182 | ModTime: object.LastModified,
183 | Size: object.Size,
184 | IsDir: object.Size == 0,
185 | Name: objectRel(object.Key),
186 | }
187 | for k, _ := range object.Metadata {
188 | header[k] = object.Metadata.Get(k)
189 | }
190 | files = append(files, file)
191 | }
192 | return
193 | }
194 |
--------------------------------------------------------------------------------
/qiniu.go:
--------------------------------------------------------------------------------
1 | package CloudStore
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "strings"
13 | "time"
14 |
15 | "github.com/astaxie/beego/httplib"
16 | "github.com/qiniu/api.v7/v7/auth/qbox"
17 | "github.com/qiniu/api.v7/v7/storage"
18 | )
19 |
20 | type QINIU struct {
21 | AccessKey string
22 | SecretKey string
23 | Bucket string
24 | Domain string
25 | Zone *storage.Zone
26 | mac *qbox.Mac
27 | BucketManager *storage.BucketManager
28 | }
29 |
30 | func NewQINIU(accessKey, secretKey, bucket, domain string) (q *QINIU, err error) {
31 | q = &QINIU{
32 | AccessKey: accessKey,
33 | SecretKey: secretKey,
34 | Bucket: bucket,
35 | Domain: domain,
36 | }
37 | q.Domain = strings.TrimRight(q.Domain, "/")
38 | q.mac = qbox.NewMac(accessKey, secretKey)
39 | q.Zone, err = storage.GetZone(accessKey, bucket)
40 | if err != nil {
41 | return
42 | }
43 | q.BucketManager = storage.NewBucketManager(q.mac, &storage.Config{Zone: q.Zone})
44 | return
45 | }
46 |
47 | func (q *QINIU) IsExist(object string) (err error) {
48 | _, err = q.GetInfo(object)
49 | return
50 | }
51 |
52 | // TODO: 目前没发现有可以设置header的地方
53 | func (q *QINIU) Upload(tmpFile, saveFile string, headers ...map[string]string) (err error) {
54 | policy := storage.PutPolicy{Scope: q.Bucket}
55 | token := policy.UploadToken(q.mac)
56 | cfg := &storage.Config{
57 | Zone: q.Zone,
58 | }
59 | form := storage.NewFormUploader(cfg)
60 | ret := &storage.PutRet{}
61 | params := make(map[string]string)
62 | for _, header := range headers {
63 | for k, v := range header {
64 | params["x:"+k] = v
65 | }
66 | }
67 | extra := &storage.PutExtra{
68 | Params: params,
69 | }
70 | saveFile = objectRel(saveFile)
71 | // 需要先删除,文件已存在的话,没法覆盖
72 | q.Delete(saveFile)
73 | err = form.PutFile(context.Background(), ret, token, saveFile, tmpFile, extra)
74 | return
75 | }
76 |
77 | func (q *QINIU) Delete(objects ...string) (err error) {
78 | length := len(objects)
79 | if length == 0 {
80 | return
81 | }
82 |
83 | defer func() {
84 | // 被删除文件不存在的时候,err值为空但不为nil,这里处理一下
85 | if err != nil && err.Error() == "" {
86 | err = nil
87 | }
88 | }()
89 |
90 | deleteOps := make([]string, 0, length)
91 | for _, object := range objects {
92 | deleteOps = append(deleteOps, storage.URIDelete(q.Bucket, objectRel(object)))
93 | }
94 | cfg := &storage.Config{
95 | Zone: q.Zone,
96 | }
97 | manager := storage.NewBucketManager(q.mac, cfg)
98 | var res []storage.BatchOpRet
99 | res, err = manager.Batch(deleteOps)
100 | if err != nil {
101 | return
102 | }
103 |
104 | var errs []string
105 | for _, item := range res {
106 | if item.Code != http.StatusOK {
107 | errs = append(errs, fmt.Errorf("%+v: %v", item.Data, item.Code).Error())
108 | }
109 | }
110 |
111 | if len(errs) > 0 {
112 | err = errors.New(strings.Join(errs, "; "))
113 | }
114 |
115 | return
116 | }
117 |
118 | func (q *QINIU) GetSignURL(object string, expire int64) (link string, err error) {
119 | object = objectRel(object)
120 | if expire > 0 {
121 | deadline := time.Now().Add(time.Second * time.Duration(expire)).Unix()
122 | link = storage.MakePrivateURL(q.mac, q.Domain, object, deadline)
123 | } else {
124 | link = storage.MakePublicURL(q.Domain, object)
125 | }
126 |
127 | if !strings.HasPrefix(link, q.Domain) {
128 | if u, errU := url.Parse(link); errU == nil {
129 | link = q.Domain + u.RequestURI()
130 | }
131 | }
132 |
133 | return
134 | }
135 |
136 | func (q *QINIU) Download(object string, savePath string) (err error) {
137 | var link string
138 | link, err = q.GetSignURL(object, 3600)
139 | if err != nil {
140 | return
141 | }
142 | req := httplib.Get(link).SetTimeout(30*time.Minute, 30*time.Minute)
143 | if strings.HasPrefix(strings.ToLower(link), "https://") {
144 | req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
145 | }
146 |
147 | var resp *http.Response
148 |
149 | resp, err = req.Response()
150 | if err != nil {
151 | return
152 | }
153 | defer resp.Body.Close()
154 |
155 | data, _ := ioutil.ReadAll(resp.Body)
156 |
157 | if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
158 | return fmt.Errorf("%v: %v", resp.Status, string(data))
159 | }
160 |
161 | err = ioutil.WriteFile(savePath, data, os.ModePerm)
162 |
163 | return
164 | }
165 |
166 | func (q *QINIU) GetInfo(object string) (info File, err error) {
167 | var fileInfo storage.FileInfo
168 |
169 | object = objectRel(object)
170 | fileInfo, err = q.BucketManager.Stat(q.Bucket, object)
171 | if err != nil {
172 | return
173 | }
174 | info = File{
175 | Name: object,
176 | Size: fileInfo.Fsize,
177 | ModTime: storage.ParsePutTime(fileInfo.PutTime),
178 | IsDir: fileInfo.Fsize == 0,
179 | }
180 | return
181 | }
182 |
183 | func (q *QINIU) Lists(prefix string) (files []File, err error) {
184 | var items []storage.ListItem
185 |
186 | prefix = objectRel(prefix)
187 | limit := 1000
188 | cfg := &storage.Config{
189 | Zone: q.Zone,
190 | }
191 |
192 | manager := storage.NewBucketManager(q.mac, cfg)
193 | items, _, _, _, err = manager.ListFiles(q.Bucket, prefix, "", "", limit)
194 | if err != nil {
195 | return
196 | }
197 |
198 | for _, item := range items {
199 | files = append(files, File{
200 | ModTime: storage.ParsePutTime(item.PutTime),
201 | Name: objectRel(item.Key),
202 | Size: item.Fsize,
203 | IsDir: item.Fsize == 0,
204 | })
205 | }
206 |
207 | return
208 | }
209 |
--------------------------------------------------------------------------------
/obs/log.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "os"
8 | "path/filepath"
9 | "runtime"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | type Level int
15 |
16 | const (
17 | LEVEL_OFF Level = 500
18 | LEVEL_ERROR Level = 400
19 | LEVEL_WARN Level = 300
20 | LEVEL_INFO Level = 200
21 | LEVEL_DEBUG Level = 100
22 | )
23 |
24 | var logLevelMap = map[Level]string{
25 | LEVEL_OFF: "[OFF]: ",
26 | LEVEL_ERROR: "[ERROR]: ",
27 | LEVEL_WARN: "[WARN]: ",
28 | LEVEL_INFO: "[INFO]: ",
29 | LEVEL_DEBUG: "[DEBUG]: ",
30 | }
31 |
32 | type logConfType struct {
33 | level Level
34 | logToConsole bool
35 | logFullPath string
36 | maxLogSize int64
37 | backups int
38 | }
39 |
40 | func getDefaultLogConf() logConfType {
41 | return logConfType{
42 | level: LEVEL_WARN,
43 | logToConsole: false,
44 | logFullPath: "",
45 | maxLogSize: 1024 * 1024 * 30, //30MB
46 | backups: 10,
47 | }
48 | }
49 |
50 | var logConf logConfType
51 |
52 | type loggerWrapper struct {
53 | fullPath string
54 | fd *os.File
55 | ch chan string
56 | wg sync.WaitGroup
57 | queue []string
58 | logger *log.Logger
59 | index int
60 | cacheCount int
61 | closed bool
62 | }
63 |
64 | func (lw *loggerWrapper) doInit() {
65 | lw.queue = make([]string, 0, lw.cacheCount)
66 | lw.logger = log.New(lw.fd, "", 0)
67 | lw.ch = make(chan string, lw.cacheCount)
68 | lw.wg.Add(1)
69 | go lw.doWrite()
70 | }
71 |
72 | func (lw *loggerWrapper) rotate() {
73 | stat, err := lw.fd.Stat()
74 | if err != nil {
75 | lw.fd.Close()
76 | panic(err)
77 | }
78 | if stat.Size() >= logConf.maxLogSize {
79 | lw.fd.Sync()
80 | lw.fd.Close()
81 | if lw.index > logConf.backups {
82 | lw.index = 1
83 | }
84 | os.Rename(lw.fullPath, lw.fullPath+"."+IntToString(lw.index))
85 | lw.index += 1
86 |
87 | fd, err := os.OpenFile(lw.fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
88 | if err != nil {
89 | panic(err)
90 | }
91 | lw.fd = fd
92 | lw.logger.SetOutput(lw.fd)
93 | }
94 | }
95 |
96 | func (lw *loggerWrapper) doFlush() {
97 | lw.rotate()
98 | for _, m := range lw.queue {
99 | lw.logger.Println(m)
100 | }
101 | lw.fd.Sync()
102 | }
103 |
104 | func (lw *loggerWrapper) doClose() {
105 | lw.closed = true
106 | close(lw.ch)
107 | lw.wg.Wait()
108 | }
109 |
110 | func (lw *loggerWrapper) doWrite() {
111 | defer lw.wg.Done()
112 | for {
113 | msg, ok := <-lw.ch
114 | if !ok {
115 | lw.doFlush()
116 | lw.fd.Close()
117 | break
118 | }
119 | if len(lw.queue) >= lw.cacheCount {
120 | lw.doFlush()
121 | lw.queue = make([]string, 0, lw.cacheCount)
122 | }
123 | lw.queue = append(lw.queue, msg)
124 | }
125 |
126 | }
127 |
128 | func (lw *loggerWrapper) Printf(format string, v ...interface{}) {
129 | if !lw.closed {
130 | msg := fmt.Sprintf(format, v...)
131 | lw.ch <- msg
132 | }
133 | }
134 |
135 | var consoleLogger *log.Logger
136 | var fileLogger *loggerWrapper
137 | var lock *sync.RWMutex = new(sync.RWMutex)
138 |
139 | func isDebugLogEnabled() bool {
140 | return logConf.level <= LEVEL_DEBUG
141 | }
142 |
143 | func isErrorLogEnabled() bool {
144 | return logConf.level <= LEVEL_ERROR
145 | }
146 |
147 | func isWarnLogEnabled() bool {
148 | return logConf.level <= LEVEL_WARN
149 | }
150 |
151 | func isInfoLogEnabled() bool {
152 | return logConf.level <= LEVEL_INFO
153 | }
154 |
155 | func reset() {
156 | if fileLogger != nil {
157 | fileLogger.doClose()
158 | fileLogger = nil
159 | }
160 | consoleLogger = nil
161 | logConf = getDefaultLogConf()
162 | }
163 |
164 | func InitLog(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool) error {
165 | return InitLogWithCacheCnt(logFullPath, maxLogSize, backups, level, logToConsole, 50)
166 | }
167 |
168 | func InitLogWithCacheCnt(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, cacheCnt int) error {
169 | lock.Lock()
170 | defer lock.Unlock()
171 | if cacheCnt <= 0 {
172 | cacheCnt = 50
173 | }
174 | reset()
175 | if fullPath := strings.TrimSpace(logFullPath); fullPath != "" {
176 | _fullPath, err := filepath.Abs(fullPath)
177 | if err != nil {
178 | return err
179 | }
180 |
181 | if !strings.HasSuffix(_fullPath, ".log") {
182 | _fullPath += ".log"
183 | }
184 |
185 | stat, err := os.Stat(_fullPath)
186 | if err == nil && stat.IsDir() {
187 | return errors.New(fmt.Sprintf("logFullPath:[%s] is a directory", _fullPath))
188 | } else if err := os.MkdirAll(filepath.Dir(_fullPath), os.ModePerm); err != nil {
189 | return err
190 | }
191 |
192 | fd, err := os.OpenFile(_fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
193 | if err != nil {
194 | return err
195 | }
196 |
197 | if stat == nil {
198 | stat, err = os.Stat(_fullPath)
199 | if err != nil {
200 | fd.Close()
201 | return err
202 | }
203 | }
204 |
205 | prefix := stat.Name() + "."
206 | index := 1
207 | walkFunc := func(path string, info os.FileInfo, err error) error {
208 | if err == nil {
209 | if name := info.Name(); strings.HasPrefix(name, prefix) {
210 | if i := StringToInt(name[len(prefix):], 0); i >= index {
211 | index = i + 1
212 | }
213 | }
214 | }
215 | return err
216 | }
217 |
218 | if err = filepath.Walk(filepath.Dir(_fullPath), walkFunc); err != nil {
219 | fd.Close()
220 | return err
221 | }
222 |
223 | fileLogger = &loggerWrapper{fullPath: _fullPath, fd: fd, index: index, cacheCount: cacheCnt, closed: false}
224 | fileLogger.doInit()
225 | }
226 | if maxLogSize > 0 {
227 | logConf.maxLogSize = maxLogSize
228 | }
229 | if backups > 0 {
230 | logConf.backups = backups
231 | }
232 | logConf.level = level
233 | if logToConsole {
234 | consoleLogger = log.New(os.Stdout, "", log.LstdFlags)
235 | }
236 | return nil
237 | }
238 |
239 | func CloseLog() {
240 | if logEnabled() {
241 | lock.Lock()
242 | defer lock.Unlock()
243 | reset()
244 | }
245 | }
246 |
247 | func SyncLog() {
248 | }
249 |
250 | func logEnabled() bool {
251 | return consoleLogger != nil || fileLogger != nil
252 | }
253 |
254 | func DoLog(level Level, format string, v ...interface{}) {
255 | doLog(level, format, v)
256 | }
257 |
258 | func doLog(level Level, format string, v ...interface{}) {
259 | if logEnabled() && logConf.level <= level {
260 | msg := fmt.Sprintf(format, v...)
261 | if _, file, line, ok := runtime.Caller(1); ok {
262 | index := strings.LastIndex(file, "/")
263 | if index >= 0 {
264 | file = file[index+1:]
265 | }
266 | msg = fmt.Sprintf("%s:%d|%s", file, line, msg)
267 | }
268 | prefix := logLevelMap[level]
269 | if consoleLogger != nil {
270 | consoleLogger.Printf("%s%s", prefix, msg)
271 | }
272 | if fileLogger != nil {
273 | nowDate := FormatUtcNow("2006-01-02T15:04:05Z")
274 | fileLogger.Printf("%s %s%s", nowDate, prefix, msg)
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/obs/conf.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "crypto/x509"
7 | "errors"
8 | "fmt"
9 | "net"
10 | "net/http"
11 | "net/url"
12 | "sort"
13 | "strconv"
14 | "strings"
15 | "time"
16 | )
17 |
18 | type securityProvider struct {
19 | ak string
20 | sk string
21 | securityToken string
22 | }
23 |
24 | type urlHolder struct {
25 | scheme string
26 | host string
27 | port int
28 | }
29 |
30 | type config struct {
31 | securityProvider *securityProvider
32 | urlHolder *urlHolder
33 | endpoint string
34 | signature SignatureType
35 | pathStyle bool
36 | region string
37 | connectTimeout int
38 | socketTimeout int
39 | headerTimeout int
40 | idleConnTimeout int
41 | finalTimeout int
42 | maxRetryCount int
43 | proxyUrl string
44 | maxConnsPerHost int
45 | sslVerify bool
46 | pemCerts []byte
47 | transport *http.Transport
48 | ctx context.Context
49 | cname bool
50 | }
51 |
52 | func (conf config) String() string {
53 | return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
54 | "\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
55 | "\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, proxyUrl:%s]",
56 | conf.endpoint, conf.signature, conf.pathStyle, conf.region,
57 | conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
58 | conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.proxyUrl,
59 | )
60 | }
61 |
62 | type configurer func(conf *config)
63 |
64 | func WithSslVerify(sslVerify bool) configurer {
65 | return WithSslVerifyAndPemCerts(sslVerify, nil)
66 | }
67 |
68 | func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
69 | return func(conf *config) {
70 | conf.sslVerify = sslVerify
71 | conf.pemCerts = pemCerts
72 | }
73 | }
74 |
75 | func WithHeaderTimeout(headerTimeout int) configurer {
76 | return func(conf *config) {
77 | conf.headerTimeout = headerTimeout
78 | }
79 | }
80 |
81 | func WithProxyUrl(proxyUrl string) configurer {
82 | return func(conf *config) {
83 | conf.proxyUrl = proxyUrl
84 | }
85 | }
86 |
87 | func WithMaxConnections(maxConnsPerHost int) configurer {
88 | return func(conf *config) {
89 | conf.maxConnsPerHost = maxConnsPerHost
90 | }
91 | }
92 |
93 | func WithPathStyle(pathStyle bool) configurer {
94 | return func(conf *config) {
95 | conf.pathStyle = pathStyle
96 | }
97 | }
98 |
99 | func WithSignature(signature SignatureType) configurer {
100 | return func(conf *config) {
101 | conf.signature = signature
102 | }
103 | }
104 |
105 | func WithRegion(region string) configurer {
106 | return func(conf *config) {
107 | conf.region = region
108 | }
109 | }
110 |
111 | func WithConnectTimeout(connectTimeout int) configurer {
112 | return func(conf *config) {
113 | conf.connectTimeout = connectTimeout
114 | }
115 | }
116 |
117 | func WithSocketTimeout(socketTimeout int) configurer {
118 | return func(conf *config) {
119 | conf.socketTimeout = socketTimeout
120 | }
121 | }
122 |
123 | func WithIdleConnTimeout(idleConnTimeout int) configurer {
124 | return func(conf *config) {
125 | conf.idleConnTimeout = idleConnTimeout
126 | }
127 | }
128 |
129 | func WithMaxRetryCount(maxRetryCount int) configurer {
130 | return func(conf *config) {
131 | conf.maxRetryCount = maxRetryCount
132 | }
133 | }
134 |
135 | func WithSecurityToken(securityToken string) configurer {
136 | return func(conf *config) {
137 | conf.securityProvider.securityToken = securityToken
138 | }
139 | }
140 |
141 | func WithHttpTransport(transport *http.Transport) configurer {
142 | return func(conf *config) {
143 | conf.transport = transport
144 | }
145 | }
146 |
147 | func WithRequestContext(ctx context.Context) configurer {
148 | return func(conf *config) {
149 | conf.ctx = ctx
150 | }
151 | }
152 |
153 | func WithCustomDomainName(cname bool) configurer {
154 | return func(conf *config) {
155 | conf.cname = cname
156 | }
157 | }
158 |
159 | func (conf *config) initConfigWithDefault() error {
160 | conf.securityProvider.ak = strings.TrimSpace(conf.securityProvider.ak)
161 | conf.securityProvider.sk = strings.TrimSpace(conf.securityProvider.sk)
162 | conf.securityProvider.securityToken = strings.TrimSpace(conf.securityProvider.securityToken)
163 | conf.endpoint = strings.TrimSpace(conf.endpoint)
164 | if conf.endpoint == "" {
165 | return errors.New("endpoint is not set")
166 | }
167 |
168 | if index := strings.Index(conf.endpoint, "?"); index > 0 {
169 | conf.endpoint = conf.endpoint[:index]
170 | }
171 |
172 | for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
173 | conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
174 | }
175 |
176 | if conf.signature == "" {
177 | conf.signature = DEFAULT_SIGNATURE
178 | }
179 |
180 | urlHolder := &urlHolder{}
181 | var address string
182 | if strings.HasPrefix(conf.endpoint, "https://") {
183 | urlHolder.scheme = "https"
184 | address = conf.endpoint[len("https://"):]
185 | } else if strings.HasPrefix(conf.endpoint, "http://") {
186 | urlHolder.scheme = "http"
187 | address = conf.endpoint[len("http://"):]
188 | } else {
189 | urlHolder.scheme = "http"
190 | address = conf.endpoint
191 | }
192 |
193 | addr := strings.Split(address, ":")
194 | if len(addr) == 2 {
195 | if port, err := strconv.Atoi(addr[1]); err == nil {
196 | urlHolder.port = port
197 | }
198 | }
199 | urlHolder.host = addr[0]
200 | if urlHolder.port == 0 {
201 | if urlHolder.scheme == "https" {
202 | urlHolder.port = 443
203 | } else {
204 | urlHolder.port = 80
205 | }
206 | }
207 |
208 | if IsIP(urlHolder.host) {
209 | conf.pathStyle = true
210 | }
211 |
212 | conf.urlHolder = urlHolder
213 |
214 | conf.region = strings.TrimSpace(conf.region)
215 | if conf.region == "" {
216 | conf.region = DEFAULT_REGION
217 | }
218 |
219 | if conf.connectTimeout <= 0 {
220 | conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
221 | }
222 |
223 | if conf.socketTimeout <= 0 {
224 | conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
225 | }
226 |
227 | conf.finalTimeout = conf.socketTimeout * 10
228 |
229 | if conf.headerTimeout <= 0 {
230 | conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
231 | }
232 |
233 | if conf.idleConnTimeout < 0 {
234 | conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
235 | }
236 |
237 | if conf.maxRetryCount < 0 {
238 | conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
239 | }
240 |
241 | if conf.maxConnsPerHost <= 0 {
242 | conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
243 | }
244 |
245 | conf.proxyUrl = strings.TrimSpace(conf.proxyUrl)
246 | return nil
247 | }
248 |
249 | func (conf *config) getTransport() error {
250 | if conf.transport == nil {
251 | conf.transport = &http.Transport{
252 | Dial: func(network, addr string) (net.Conn, error) {
253 | conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
254 | if err != nil {
255 | return nil, err
256 | }
257 | return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
258 | },
259 | MaxIdleConns: conf.maxConnsPerHost,
260 | MaxIdleConnsPerHost: conf.maxConnsPerHost,
261 | ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
262 | IdleConnTimeout: time.Second * time.Duration(conf.idleConnTimeout),
263 | }
264 |
265 | if conf.proxyUrl != "" {
266 | proxyUrl, err := url.Parse(conf.proxyUrl)
267 | if err != nil {
268 | return err
269 | }
270 | conf.transport.Proxy = http.ProxyURL(proxyUrl)
271 | }
272 |
273 | tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
274 | if conf.sslVerify && conf.pemCerts != nil {
275 | pool := x509.NewCertPool()
276 | pool.AppendCertsFromPEM(conf.pemCerts)
277 | tlsConfig.RootCAs = pool
278 | }
279 |
280 | conf.transport.TLSClientConfig = tlsConfig
281 | }
282 |
283 | return nil
284 | }
285 |
286 | func checkRedirectFunc(req *http.Request, via []*http.Request) error {
287 | return http.ErrUseLastResponse
288 | }
289 |
290 | func DummyQueryEscape(s string) string {
291 | return s
292 | }
293 |
294 | func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestUrl string, canonicalizedUrl string) {
295 |
296 | urlHolder := conf.urlHolder
297 | if conf.cname {
298 | requestUrl = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
299 | if conf.signature == "v4" {
300 | canonicalizedUrl = "/"
301 | } else {
302 | canonicalizedUrl = "/" + urlHolder.host +"/"
303 | }
304 | } else {
305 | if bucketName == "" {
306 | requestUrl = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
307 | canonicalizedUrl = "/"
308 | } else {
309 | if conf.pathStyle {
310 | requestUrl = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
311 | canonicalizedUrl = "/" + bucketName
312 | } else {
313 | requestUrl = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
314 | if conf.signature == "v2" || conf.signature == "OBS"{
315 | canonicalizedUrl = "/" + bucketName + "/"
316 | } else {
317 | canonicalizedUrl = "/"
318 | }
319 | }
320 | }
321 | }
322 | var escapeFunc func(s string) string
323 | if escape {
324 | escapeFunc = url.QueryEscape
325 | } else {
326 | escapeFunc = DummyQueryEscape
327 | }
328 |
329 | if objectKey != "" {
330 | encodeObjectKey := escapeFunc(objectKey)
331 | requestUrl += "/" + encodeObjectKey
332 | if !strings.HasSuffix(canonicalizedUrl, "/") {
333 | canonicalizedUrl += "/"
334 | }
335 | canonicalizedUrl += encodeObjectKey
336 | }
337 |
338 | keys := make([]string, 0, len(params))
339 | for key, _ := range params {
340 | keys = append(keys, strings.TrimSpace(key))
341 | }
342 | sort.Strings(keys)
343 | i := 0
344 |
345 | for index, key := range keys {
346 | if index == 0 {
347 | requestUrl += "?"
348 | } else {
349 | requestUrl += "&"
350 | }
351 | _key := url.QueryEscape(key)
352 | requestUrl += _key
353 |
354 | _value := params[key]
355 | if conf.signature == "v4" {
356 | requestUrl += "=" + url.QueryEscape(_value)
357 | } else {
358 | if _value != "" {
359 | requestUrl += "=" + url.QueryEscape(_value)
360 | _value = "=" + _value
361 | } else {
362 | _value = ""
363 | }
364 | lowerKey := strings.ToLower(key)
365 | _, ok := allowed_resource_parameter_names[lowerKey]
366 | prefixHeader := HEADER_PREFIX
367 | isObs := conf.signature == SignatureObs
368 | if isObs {
369 | prefixHeader = HEADER_PREFIX_OBS
370 | }
371 | ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
372 | if ok {
373 | if i == 0 {
374 | canonicalizedUrl += "?"
375 | } else {
376 | canonicalizedUrl += "&"
377 | }
378 | canonicalizedUrl += getQueryUrl(_key, _value)
379 | i++
380 | }
381 | }
382 | }
383 | return
384 | }
385 |
386 | func getQueryUrl(key, value string) string {
387 | queryUrl := ""
388 | queryUrl += key
389 | queryUrl += value
390 | return queryUrl
391 | }
392 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/obs/util.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "crypto/hmac"
5 | "crypto/md5"
6 | "crypto/sha1"
7 | "crypto/sha256"
8 | "encoding/base64"
9 | "encoding/hex"
10 | "encoding/xml"
11 | "fmt"
12 | "net/url"
13 | "regexp"
14 | "strconv"
15 | "strings"
16 | "time"
17 | )
18 |
19 | var regex = regexp.MustCompile("^[\u4e00-\u9fa5]$")
20 | var ipRegex = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
21 | var v4AuthRegex = regexp.MustCompile("Credential=(.+?),SignedHeaders=(.+?),Signature=.+")
22 | var regionRegex = regexp.MustCompile(".+/\\d+/(.+?)/.+")
23 |
24 | func StringContains(src string, subStr string, subTranscoding string) string {
25 | return strings.Replace(src, subStr, subTranscoding, -1)
26 | }
27 | func XmlTranscoding(src string) string {
28 | srcTmp := StringContains(src, "&", "&")
29 | srcTmp = StringContains(srcTmp, "<", "<")
30 | srcTmp = StringContains(srcTmp, ">", ">")
31 | srcTmp = StringContains(srcTmp, "'", "'")
32 | srcTmp = StringContains(srcTmp, "\"", """)
33 | return srcTmp
34 | }
35 | func StringToInt(value string, def int) int {
36 | ret, err := strconv.Atoi(value)
37 | if err != nil {
38 | ret = def
39 | }
40 | return ret
41 | }
42 |
43 | func StringToInt64(value string, def int64) int64 {
44 | ret, err := strconv.ParseInt(value, 10, 64)
45 | if err != nil {
46 | ret = def
47 | }
48 | return ret
49 | }
50 |
51 | func IntToString(value int) string {
52 | return strconv.Itoa(value)
53 | }
54 |
55 | func Int64ToString(value int64) string {
56 | return strconv.FormatInt(value, 10)
57 | }
58 |
59 | func GetCurrentTimestamp() int64 {
60 | return time.Now().UnixNano() / 1000000
61 | }
62 |
63 | func FormatUtcNow(format string) string {
64 | return time.Now().UTC().Format(format)
65 | }
66 |
67 | func FormatUtcToRfc1123(t time.Time) string {
68 | ret := t.UTC().Format(time.RFC1123)
69 | return ret[:strings.LastIndex(ret, "UTC")] + "GMT"
70 | }
71 |
72 | func Md5(value []byte) []byte {
73 | m := md5.New()
74 | m.Write(value)
75 | return m.Sum(nil)
76 | }
77 |
78 | func HmacSha1(key, value []byte) []byte {
79 | mac := hmac.New(sha1.New, key)
80 | mac.Write(value)
81 | return mac.Sum(nil)
82 | }
83 |
84 | func HmacSha256(key, value []byte) []byte {
85 | mac := hmac.New(sha256.New, key)
86 | mac.Write(value)
87 | return mac.Sum(nil)
88 | }
89 |
90 | func Base64Encode(value []byte) string {
91 | return base64.StdEncoding.EncodeToString(value)
92 | }
93 |
94 | func Base64Decode(value string) ([]byte, error) {
95 | return base64.StdEncoding.DecodeString(value)
96 | }
97 |
98 | func HexMd5(value []byte) string {
99 | return Hex(Md5(value))
100 | }
101 |
102 | func Base64Md5(value []byte) string {
103 | return Base64Encode(Md5(value))
104 | }
105 |
106 | func Sha256Hash(value []byte) []byte {
107 | hash := sha256.New()
108 | hash.Write(value)
109 | return hash.Sum(nil)
110 | }
111 |
112 | func ParseXml(value []byte, result interface{}) error {
113 | if len(value) == 0 {
114 | return nil
115 | }
116 | return xml.Unmarshal(value, result)
117 | }
118 |
119 | func TransToXml(value interface{}) ([]byte, error) {
120 | if value == nil {
121 | return []byte{}, nil
122 | }
123 | return xml.Marshal(value)
124 | }
125 |
126 | func Hex(value []byte) string {
127 | return hex.EncodeToString(value)
128 | }
129 |
130 | func HexSha256(value []byte) string {
131 | return Hex(Sha256Hash(value))
132 | }
133 |
134 | func UrlDecode(value string) (string, error) {
135 | ret, err := url.QueryUnescape(value)
136 | if err == nil {
137 | return ret, nil
138 | }
139 | return "", err
140 | }
141 |
142 | func IsIP(value string) bool {
143 | return ipRegex.MatchString(value)
144 | }
145 |
146 | func UrlEncode(value string, chineseOnly bool) string {
147 | if chineseOnly {
148 | values := make([]string, 0, len(value))
149 | for _, val := range value {
150 | _value := string(val)
151 | if regex.MatchString(_value) {
152 | _value = url.QueryEscape(_value)
153 | }
154 | values = append(values, _value)
155 | }
156 | return strings.Join(values, "")
157 | }
158 | return url.QueryEscape(value)
159 | }
160 |
161 | func copyHeaders(m map[string][]string) (ret map[string][]string) {
162 | if m != nil {
163 | ret = make(map[string][]string, len(m))
164 | for key, values := range m {
165 | _values := make([]string, 0, len(values))
166 | for _, value := range values {
167 | _values = append(_values, value)
168 | }
169 | ret[strings.ToLower(key)] = _values
170 | }
171 | } else {
172 | ret = make(map[string][]string)
173 | }
174 |
175 | return
176 | }
177 |
178 | func parseHeaders(headers map[string][]string) (signature string, region string, signedHeaders string) {
179 | signature = "v2"
180 | if receviedAuthorization, ok := headers[strings.ToLower(HEADER_AUTH_CAMEL)]; ok && len(receviedAuthorization) > 0 {
181 | if strings.HasPrefix(receviedAuthorization[0], V4_HASH_PREFIX) {
182 | signature = "v4"
183 | matches := v4AuthRegex.FindStringSubmatch(receviedAuthorization[0])
184 | if len(matches) >= 3 {
185 | region = matches[1]
186 | regions := regionRegex.FindStringSubmatch(region)
187 | if len(regions) >= 2 {
188 | region = regions[1]
189 | }
190 | signedHeaders = matches[2]
191 | }
192 |
193 | } else if strings.HasPrefix(receviedAuthorization[0], V2_HASH_PREFIX) {
194 | signature = "v2"
195 | }
196 | }
197 | return
198 | }
199 |
200 | func getTemporaryKeys() []string {
201 | return []string{
202 | "Signature",
203 | "signature",
204 | "X-Amz-Signature",
205 | "x-amz-signature",
206 | }
207 | }
208 |
209 | func getIsObs(isTemporary bool,querys []string,headers map[string][]string) bool{
210 | isObs :=true
211 | if isTemporary {
212 | for _, value := range querys {
213 | keyPrefix:= strings.ToLower(value)
214 | if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
215 | isObs = false
216 | }else if strings.HasPrefix(value, HEADER_ACCESSS_KEY_AMZ) {
217 | isObs = false
218 | }
219 | }
220 | } else {
221 | for key,_ := range headers {
222 | keyPrefix:= strings.ToLower(key)
223 | if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
224 | isObs = false
225 | break
226 | }
227 | }
228 | }
229 | return isObs
230 | }
231 |
232 | func GetAuthorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) {
233 |
234 | if strings.HasPrefix(queryUrl, "?") {
235 | queryUrl = queryUrl[1:]
236 | }
237 |
238 | method = strings.ToUpper(method)
239 |
240 | querys := strings.Split(queryUrl, "&")
241 | querysResult := make([]string,0)
242 | for _, value := range querys {
243 | if value != "=" && len(value) != 0 {
244 | querysResult=append(querysResult,value);
245 | }
246 | }
247 | params := make(map[string]string)
248 |
249 | for _, value := range querysResult {
250 | kv := strings.Split(value, "=")
251 | length := len(kv)
252 | if length == 1 {
253 | key, _ := UrlDecode(kv[0])
254 | params[key] = ""
255 | } else if length >= 2 {
256 | key, _ := UrlDecode(kv[0])
257 | vals := make([]string, 0, length-1)
258 | for i := 1; i < length; i++ {
259 | val, _ := UrlDecode(kv[i])
260 | vals = append(vals, val)
261 | }
262 | params[key] = strings.Join(vals, "=")
263 | }
264 | }
265 | isTemporary := false
266 | signature := "v2"
267 | temporaryKeys := getTemporaryKeys()
268 | for _, key := range temporaryKeys {
269 | if _, ok := params[key]; ok {
270 | isTemporary = true
271 | if strings.ToLower(key) == "signature" {
272 | signature = "v2"
273 | } else if strings.ToLower(key) == "x-amz-signature" {
274 | signature = "v4"
275 | }
276 | break
277 | }
278 | }
279 | isObs := getIsObs(isTemporary,querysResult,headers)
280 | headers = copyHeaders(headers)
281 | pathStyle := false
282 | if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") {
283 | pathStyle = true
284 | }
285 | conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk},
286 | urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
287 | pathStyle: pathStyle}
288 |
289 | if isTemporary {
290 | return getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature, conf, params, headers, isObs)
291 | } else {
292 | signature, region, signedHeaders := parseHeaders(headers)
293 | if signature == "v4" {
294 | conf.signature = SignatureV4
295 | requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
296 | parsedRequestUrl, _ := url.Parse(requestUrl)
297 | headerKeys := strings.Split(signedHeaders, ";")
298 | _headers := make(map[string][]string, len(headerKeys))
299 | for _, headerKey := range headerKeys {
300 | _headers[headerKey] = headers[headerKey]
301 | }
302 | ret = v4Auth(ak, sk, region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, _headers)
303 | ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
304 | } else if signature == "v2" {
305 | conf.signature = SignatureV2
306 | _, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
307 | ret = v2Auth(ak, sk, method, canonicalizedUrl, headers,isObs)
308 | v2HashPrefix:=V2_HASH_PREFIX
309 | if isObs {
310 | v2HashPrefix = OBS_HASH_PREFIX
311 | }
312 | ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
313 | }
314 | return
315 | }
316 |
317 | }
318 |
319 | func getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature string, conf *config, params map[string]string,
320 | headers map[string][]string, isObs bool) (ret map[string]string) {
321 |
322 |
323 | if signature == "v4" {
324 | conf.signature = SignatureV4
325 |
326 | longDate, ok := params[PARAM_DATE_AMZ_CAMEL]
327 | if !ok {
328 | longDate = params[HEADER_DATE_AMZ]
329 | }
330 | shortDate := longDate[:8]
331 |
332 | credential, ok := params[PARAM_CREDENTIAL_AMZ_CAMEL]
333 | if !ok {
334 | credential = params[strings.ToLower(PARAM_CREDENTIAL_AMZ_CAMEL)]
335 | }
336 |
337 | _credential, _ := UrlDecode(credential)
338 |
339 | regions := regionRegex.FindStringSubmatch(_credential)
340 | var region string
341 | if len(regions) >= 2 {
342 | region = regions[1]
343 | }
344 |
345 | _, scope := getCredential(ak, region, shortDate)
346 |
347 | expires, ok := params[PARAM_EXPIRES_AMZ_CAMEL]
348 | if !ok {
349 | expires = params[strings.ToLower(PARAM_EXPIRES_AMZ_CAMEL)]
350 | }
351 |
352 | signedHeaders, ok := params[PARAM_SIGNEDHEADERS_AMZ_CAMEL]
353 | if !ok {
354 | signedHeaders = params[strings.ToLower(PARAM_SIGNEDHEADERS_AMZ_CAMEL)]
355 | }
356 |
357 | algorithm, ok := params[PARAM_ALGORITHM_AMZ_CAMEL]
358 | if !ok {
359 | algorithm = params[strings.ToLower(PARAM_ALGORITHM_AMZ_CAMEL)]
360 | }
361 |
362 | if _, ok := params[PARAM_SIGNATURE_AMZ_CAMEL]; ok {
363 | delete(params, PARAM_SIGNATURE_AMZ_CAMEL)
364 | } else if _, ok := params[strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL)]; ok {
365 | delete(params, strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL))
366 | }
367 |
368 | ret = make(map[string]string, 6)
369 | ret[PARAM_ALGORITHM_AMZ_CAMEL] = algorithm
370 | ret[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
371 | ret[PARAM_DATE_AMZ_CAMEL] = longDate
372 | ret[PARAM_EXPIRES_AMZ_CAMEL] = expires
373 | ret[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = signedHeaders
374 |
375 | requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
376 | parsedRequestUrl, _ := url.Parse(requestUrl)
377 | stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, strings.Split(signedHeaders, ";"), headers)
378 | ret[PARAM_SIGNATURE_AMZ_CAMEL] = UrlEncode(getSignature(stringToSign, sk, region, shortDate), false)
379 | } else if signature == "v2" {
380 | conf.signature = SignatureV2
381 | _, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
382 | expires, ok := params["Expires"]
383 | if !ok {
384 | expires = params["expires"]
385 | }
386 | headers[HEADER_DATE_CAMEL] = []string{expires}
387 | stringToSign := getV2StringToSign(method, canonicalizedUrl, headers,isObs)
388 | ret = make(map[string]string, 3)
389 | ret["Signature"] = UrlEncode(Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign))), false)
390 | ret["AWSAccessKeyId"] = UrlEncode(ak, false)
391 | ret["Expires"] = UrlEncode(expires, false)
392 | }
393 |
394 | return
395 | }
396 |
--------------------------------------------------------------------------------
/obs/auth.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "sort"
7 | "strings"
8 | "time"
9 | )
10 |
11 | func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, params map[string]string,
12 | headers map[string][]string, expires int64) (requestUrl string, err error) {
13 |
14 | requestUrl, canonicalizedUrl := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
15 | parsedRequestUrl, err := url.Parse(requestUrl)
16 | if err != nil {
17 | return "", err
18 | }
19 | encodeHeaders(headers)
20 | hostName := parsedRequestUrl.Host
21 |
22 | isV4 := obsClient.conf.signature == SignatureV4
23 | prepareHostAndDate(headers, hostName, isV4)
24 |
25 | if obsClient.conf.securityProvider == nil || obsClient.conf.securityProvider.ak == "" || obsClient.conf.securityProvider.sk == "" {
26 | doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
27 | } else {
28 | if obsClient.conf.securityProvider.securityToken != "" {
29 | params[HEADER_STS_TOKEN_AMZ] = obsClient.conf.securityProvider.securityToken
30 | }
31 |
32 | if isV4 {
33 | date, _ := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0])
34 | delete(headers, HEADER_DATE_CAMEL)
35 | shortDate := date.Format(SHORT_DATE_FORMAT)
36 | longDate := date.Format(LONG_DATE_FORMAT)
37 |
38 | signedHeaders, _headers := getSignedHeaders(headers)
39 |
40 | credential, scope := getCredential(obsClient.conf.securityProvider.ak, obsClient.conf.region, shortDate)
41 | params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
42 | params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
43 | params[PARAM_DATE_AMZ_CAMEL] = longDate
44 | params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires)
45 | params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";")
46 |
47 | requestUrl, canonicalizedUrl = obsClient.conf.formatUrls(bucketName, objectKey, params, true)
48 | parsedRequestUrl, _ = url.Parse(requestUrl)
49 | stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers)
50 | signature := getSignature(stringToSign, obsClient.conf.securityProvider.sk, obsClient.conf.region, shortDate)
51 |
52 | requestUrl += fmt.Sprintf("&%s=%s", PARAM_SIGNATURE_AMZ_CAMEL, UrlEncode(signature, false))
53 |
54 | } else {
55 | originDate := headers[HEADER_DATE_CAMEL][0]
56 | date, _ := time.Parse(RFC1123_FORMAT, originDate)
57 | expires += date.Unix()
58 | headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)}
59 |
60 | stringToSign := getV2StringToSign(method, canonicalizedUrl, headers,obsClient.conf.signature==SignatureObs)
61 | signature := UrlEncode(Base64Encode(HmacSha1([]byte(obsClient.conf.securityProvider.sk), []byte(stringToSign))), false)
62 | if strings.Index(requestUrl, "?") < 0 {
63 | requestUrl += "?"
64 | } else {
65 | requestUrl += "&"
66 | }
67 | delete(headers, HEADER_DATE_CAMEL)
68 | requestUrl += fmt.Sprintf("AWSAccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(obsClient.conf.securityProvider.ak, false),
69 | expires, signature)
70 | }
71 | }
72 |
73 | return
74 | }
75 |
76 | func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string,
77 | headers map[string][]string, hostName string) (requestUrl string, err error) {
78 | isObs := obsClient.conf.signature == SignatureObs
79 | requestUrl, canonicalizedUrl := obsClient.conf.formatUrls(bucketName, objectKey, params, true )
80 | parsedRequestUrl, err := url.Parse(requestUrl)
81 | if err != nil {
82 | return "", err
83 | }
84 | encodeHeaders(headers)
85 |
86 | if hostName == "" {
87 | hostName = parsedRequestUrl.Host
88 | }
89 |
90 | isV4 := obsClient.conf.signature == SignatureV4
91 | prepareHostAndDate(headers, hostName, isV4)
92 |
93 | if obsClient.conf.securityProvider == nil || obsClient.conf.securityProvider.ak == "" || obsClient.conf.securityProvider.sk == "" {
94 | doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
95 | } else {
96 | if obsClient.conf.securityProvider.securityToken != "" {
97 | headers[HEADER_STS_TOKEN_AMZ] = []string{obsClient.conf.securityProvider.securityToken}
98 | }
99 | ak := obsClient.conf.securityProvider.ak
100 | sk := obsClient.conf.securityProvider.sk
101 | var authorization string
102 | if isV4 {
103 | headers[HEADER_CONTENT_SHA256_AMZ] = []string{EMPTY_CONTENT_SHA256}
104 | ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, headers)
105 | authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
106 | } else {
107 | ret := v2Auth(ak, sk, method, canonicalizedUrl, headers, isObs)
108 | hashPrefix :=V2_HASH_PREFIX
109 | if isObs {
110 | hashPrefix = OBS_HASH_PREFIX
111 | }
112 | authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"])
113 | }
114 | headers[HEADER_AUTH_CAMEL] = []string{authorization}
115 | }
116 | return
117 | }
118 |
119 | func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) {
120 | headers[HEADER_HOST_CAMEL] = []string{hostName}
121 | if date, ok := headers[HEADER_DATE_AMZ]; ok {
122 | flag := false
123 | if len(date) == 1 {
124 | if isV4 {
125 | if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil {
126 | headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)}
127 | flag = true
128 | }
129 | } else {
130 | if strings.HasSuffix(date[0], "GMT") {
131 | headers[HEADER_DATE_CAMEL] = []string{date[0]}
132 | flag = true
133 | }
134 | }
135 | }
136 | if !flag {
137 | delete(headers, HEADER_DATE_AMZ)
138 | }
139 | }
140 | if _, ok := headers[HEADER_DATE_CAMEL]; !ok {
141 | headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())}
142 | }
143 | }
144 |
145 | func encodeHeaders(headers map[string][]string) {
146 | for key, values := range headers {
147 | for index, value := range values {
148 | values[index] = UrlEncode(value, true)
149 | }
150 | headers[key] = values
151 | }
152 | }
153 |
154 | func attachHeaders(headers map[string][]string,isObs bool) string {
155 | length := len(headers)
156 | _headers := make(map[string][]string, length)
157 | keys := make([]string, 0, length)
158 |
159 | for key, value := range headers {
160 | _key := strings.ToLower(strings.TrimSpace(key))
161 | if _key != "" {
162 | prefixheader := HEADER_PREFIX
163 | if isObs {
164 | prefixheader =HEADER_PREFIX_OBS
165 | }
166 | if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) {
167 | keys = append(keys, _key)
168 | _headers[_key] = value
169 | }
170 | } else {
171 | delete(headers, key)
172 | }
173 | }
174 |
175 | for _, interestedHeader := range interested_headers {
176 | if _, ok := _headers[interestedHeader]; !ok {
177 | _headers[interestedHeader] = []string{""}
178 | keys = append(keys, interestedHeader)
179 | }
180 | }
181 | dateCamelHeader := PARAM_DATE_AMZ_CAMEL
182 | dataHeader:=HEADER_DATE_AMZ
183 | if isObs {
184 | dateCamelHeader = PARAM_DATE_OBS_CAMEL
185 | dataHeader =HEADER_DATE_OBS
186 | }
187 | if _, ok := _headers[HEADER_DATE_CAMEL]; ok {
188 | if _, ok := _headers[dataHeader]; ok {
189 | _headers[HEADER_DATE_CAMEL] = []string{""}
190 | } else if _, ok := headers[dateCamelHeader]; ok {
191 | _headers[HEADER_DATE_CAMEL] = []string{""}
192 | }
193 | } else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
194 | if _, ok := _headers[dataHeader]; ok {
195 | _headers[HEADER_DATE_CAMEL] = []string{""}
196 | } else if _, ok := headers[dateCamelHeader]; ok {
197 | _headers[HEADER_DATE_CAMEL] = []string{""}
198 | }
199 | }
200 |
201 | sort.Strings(keys)
202 |
203 | stringToSign := make([]string, 0, len(keys))
204 | for _, key := range keys {
205 | var value string
206 | prefixHeader := HEADER_PREFIX
207 | prefixMetaHeader := HEADER_PREFIX_META
208 | if isObs {
209 | prefixHeader = HEADER_PREFIX_OBS
210 | prefixMetaHeader = HEADER_PREFIX_META_OBS
211 | }
212 | if strings.HasPrefix(key, prefixHeader) {
213 | if strings.HasPrefix(key, prefixMetaHeader) {
214 | for index, v := range _headers[key] {
215 | value += strings.TrimSpace(v)
216 | if index != len(_headers[key])-1 {
217 | value += ","
218 | }
219 | }
220 | } else {
221 | value = strings.Join(_headers[key], ",")
222 | }
223 | value = fmt.Sprintf("%s:%s", key, value)
224 | } else {
225 | value = strings.Join(_headers[key], ",")
226 | }
227 | stringToSign = append(stringToSign, value)
228 | }
229 | return strings.Join(stringToSign, "\n")
230 | }
231 |
232 | func getV2StringToSign(method, canonicalizedUrl string, headers map[string][]string, isObs bool) string {
233 | stringToSign := strings.Join([]string{method, "\n", attachHeaders(headers,isObs), "\n", canonicalizedUrl}, "")
234 | doLog(LEVEL_DEBUG, "The v2 auth stringToSign:\n%s", stringToSign)
235 | return stringToSign
236 | }
237 |
238 | func v2Auth(ak, sk, method, canonicalizedUrl string, headers map[string][]string, isObs bool) map[string]string {
239 | stringToSign := getV2StringToSign(method, canonicalizedUrl, headers,isObs)
240 | return map[string]string{"Signature": Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign)))}
241 | }
242 |
243 | func getScope(region, shortDate string) string {
244 | return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX)
245 | }
246 |
247 | func getCredential(ak, region, shortDate string) (string, string) {
248 | scope := getScope(region, shortDate)
249 | return fmt.Sprintf("%s/%s", ak, scope), scope
250 | }
251 |
252 | func getV4StringToSign(method, canonicalizedUrl, queryUrl, scope, longDate, payload string, signedHeaders []string, headers map[string][]string) string {
253 | canonicalRequest := make([]string, 0, 10+len(signedHeaders)*4)
254 | canonicalRequest = append(canonicalRequest, method)
255 | canonicalRequest = append(canonicalRequest, "\n")
256 | canonicalRequest = append(canonicalRequest, canonicalizedUrl)
257 | canonicalRequest = append(canonicalRequest, "\n")
258 | canonicalRequest = append(canonicalRequest, queryUrl)
259 | canonicalRequest = append(canonicalRequest, "\n")
260 |
261 | for _, signedHeader := range signedHeaders {
262 | values, _ := headers[signedHeader]
263 | for _, value := range values {
264 | canonicalRequest = append(canonicalRequest, signedHeader)
265 | canonicalRequest = append(canonicalRequest, ":")
266 | canonicalRequest = append(canonicalRequest, value)
267 | canonicalRequest = append(canonicalRequest, "\n")
268 | }
269 | }
270 | canonicalRequest = append(canonicalRequest, "\n")
271 | canonicalRequest = append(canonicalRequest, strings.Join(signedHeaders, ";"))
272 | canonicalRequest = append(canonicalRequest, "\n")
273 | canonicalRequest = append(canonicalRequest, payload)
274 |
275 | _canonicalRequest := strings.Join(canonicalRequest, "")
276 | doLog(LEVEL_DEBUG, "The v4 auth canonicalRequest:\n%s", _canonicalRequest)
277 |
278 | stringToSign := make([]string, 0, 7)
279 | stringToSign = append(stringToSign, V4_HASH_PREFIX)
280 | stringToSign = append(stringToSign, "\n")
281 | stringToSign = append(stringToSign, longDate)
282 | stringToSign = append(stringToSign, "\n")
283 | stringToSign = append(stringToSign, scope)
284 | stringToSign = append(stringToSign, "\n")
285 | stringToSign = append(stringToSign, HexSha256([]byte(_canonicalRequest)))
286 |
287 | _stringToSign := strings.Join(stringToSign, "")
288 |
289 | doLog(LEVEL_DEBUG, "The v4 auth stringToSign:\n%s", _stringToSign)
290 | return _stringToSign
291 | }
292 |
293 | func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) {
294 | length := len(headers)
295 | _headers := make(map[string][]string, length)
296 | signedHeaders := make([]string, 0, length)
297 | for key, value := range headers {
298 | _key := strings.ToLower(strings.TrimSpace(key))
299 | if _key != "" {
300 | signedHeaders = append(signedHeaders, _key)
301 | _headers[_key] = value
302 | } else {
303 | delete(headers, key)
304 | }
305 | }
306 | sort.Strings(signedHeaders)
307 | return signedHeaders, _headers
308 | }
309 |
310 | func getSignature(stringToSign, sk, region, shortDate string) string {
311 | key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate))
312 | key = HmacSha256(key, []byte(region))
313 | key = HmacSha256(key, []byte(V4_SERVICE_NAME))
314 | key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX))
315 | return Hex(HmacSha256(key, []byte(stringToSign)))
316 | }
317 |
318 | func V4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl string, headers map[string][]string) map[string]string {
319 | return v4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl, headers)
320 | }
321 |
322 | func v4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl string, headers map[string][]string) map[string]string {
323 | var t time.Time
324 | if val, ok := headers[HEADER_DATE_AMZ]; ok {
325 | var err error
326 | t, err = time.Parse(LONG_DATE_FORMAT, val[0])
327 | if err != nil {
328 | t = time.Now().UTC()
329 | }
330 | } else if val, ok := headers[PARAM_DATE_AMZ_CAMEL]; ok {
331 | var err error
332 | t, err = time.Parse(LONG_DATE_FORMAT, val[0])
333 | if err != nil {
334 | t = time.Now().UTC()
335 | }
336 | } else if val, ok := headers[HEADER_DATE_CAMEL]; ok {
337 | var err error
338 | t, err = time.Parse(RFC1123_FORMAT, val[0])
339 | if err != nil {
340 | t = time.Now().UTC()
341 | }
342 | } else if val, ok := headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
343 | var err error
344 | t, err = time.Parse(RFC1123_FORMAT, val[0])
345 | if err != nil {
346 | t = time.Now().UTC()
347 | }
348 | } else {
349 | t = time.Now().UTC()
350 | }
351 | shortDate := t.Format(SHORT_DATE_FORMAT)
352 | longDate := t.Format(LONG_DATE_FORMAT)
353 |
354 | signedHeaders, _headers := getSignedHeaders(headers)
355 |
356 | credential, scope := getCredential(ak, region, shortDate)
357 |
358 | payload:= EMPTY_CONTENT_SHA256
359 | if val, ok := headers[HEADER_CONTENT_SHA256_AMZ]; ok {
360 | payload = val[0]
361 | }
362 | stringToSign := getV4StringToSign(method, canonicalizedUrl, queryUrl, scope, longDate, payload, signedHeaders, _headers)
363 |
364 | signature := getSignature(stringToSign, sk, region, shortDate)
365 |
366 | ret := make(map[string]string, 3)
367 | ret["Credential"] = credential
368 | ret["SignedHeaders"] = strings.Join(signedHeaders, ";")
369 | ret["Signature"] = signature
370 | return ret
371 | }
372 |
--------------------------------------------------------------------------------
/obs/http.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "io"
7 | "math/rand"
8 | "net"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "strings"
13 | "time"
14 | )
15 |
16 | func prepareHeaders(headers map[string][]string, meta bool, isObs bool) map[string][]string {
17 | _headers := make(map[string][]string, len(headers))
18 | if headers != nil {
19 | for key, value := range headers {
20 | key = strings.TrimSpace(key)
21 | if key == "" {
22 | continue
23 | }
24 | _key := strings.ToLower(key)
25 | if _, ok := allowed_request_http_header_metadata_names[_key]; !ok && !strings.HasPrefix(key, HEADER_PREFIX) && !strings.HasPrefix(key, HEADER_PREFIX_OBS) {
26 | if !meta {
27 | continue
28 | }
29 | if !isObs {
30 | _key = HEADER_PREFIX_META + _key
31 | } else {
32 | _key = HEADER_PREFIX_META_OBS + _key
33 | }
34 | } else {
35 | _key = key
36 | }
37 | _headers[_key] = value
38 | }
39 | }
40 | return _headers
41 | }
42 |
43 | func (obsClient ObsClient) doActionWithoutBucket(action, method string, input ISerializable, output IBaseModel) error {
44 | return obsClient.doAction(action, method, "", "", input, output, true, true)
45 | }
46 |
47 | func (obsClient ObsClient) doActionWithBucketV2(action, method, bucketName string, input ISerializable, output IBaseModel) error {
48 | if strings.TrimSpace(bucketName) == "" && !obsClient.conf.cname{
49 | return errors.New("Bucket is empty")
50 | }
51 | return obsClient.doAction(action, method, bucketName, "", input, output, false, true)
52 | }
53 |
54 | func (obsClient ObsClient) doActionWithBucket(action, method, bucketName string, input ISerializable, output IBaseModel) error {
55 | if strings.TrimSpace(bucketName) == "" && !obsClient.conf.cname{
56 | return errors.New("Bucket is empty")
57 | }
58 | return obsClient.doAction(action, method, bucketName, "", input, output, true, true)
59 | }
60 |
61 | func (obsClient ObsClient) doActionWithBucketAndKey(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel) error {
62 | return obsClient._doActionWithBucketAndKey(action, method, bucketName, objectKey, input, output, true)
63 | }
64 |
65 | func (obsClient ObsClient) doActionWithBucketAndKeyUnRepeatable(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel) error {
66 | return obsClient._doActionWithBucketAndKey(action, method, bucketName, objectKey, input, output, false)
67 | }
68 |
69 | func (obsClient ObsClient) _doActionWithBucketAndKey(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel, repeatable bool) error {
70 | if strings.TrimSpace(bucketName) == "" && !obsClient.conf.cname{
71 | return errors.New("Bucket is empty")
72 | }
73 | if strings.TrimSpace(objectKey) == "" {
74 | return errors.New("Key is empty")
75 | }
76 | return obsClient.doAction(action, method, bucketName, objectKey, input, output, true, repeatable)
77 | }
78 |
79 | func (obsClient ObsClient) doAction(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel, xmlResult bool, repeatable bool) error {
80 |
81 | var resp *http.Response
82 | var respError error
83 | doLog(LEVEL_INFO, "Enter method %s...", action)
84 | start := GetCurrentTimestamp()
85 |
86 | params, headers, data := input.trans(obsClient.conf.signature == SignatureObs)
87 | if params == nil {
88 | params = make(map[string]string)
89 | }
90 |
91 | if headers == nil {
92 | headers = make(map[string][]string)
93 | }
94 |
95 | switch method {
96 | case HTTP_GET:
97 | resp, respError = obsClient.doHttpGet(bucketName, objectKey, params, headers, data, repeatable)
98 | case HTTP_POST:
99 | resp, respError = obsClient.doHttpPost(bucketName, objectKey, params, headers, data, repeatable)
100 | case HTTP_PUT:
101 | resp, respError = obsClient.doHttpPut(bucketName, objectKey, params, headers, data, repeatable)
102 | case HTTP_DELETE:
103 | resp, respError = obsClient.doHttpDelete(bucketName, objectKey, params, headers, data, repeatable)
104 | case HTTP_HEAD:
105 | resp, respError = obsClient.doHttpHead(bucketName, objectKey, params, headers, data, repeatable)
106 | case HTTP_OPTIONS:
107 | resp, respError = obsClient.doHttpOptions(bucketName, objectKey, params, headers, data, repeatable)
108 | default:
109 | respError = errors.New("Unexpect http method error")
110 | }
111 | if respError == nil && output != nil {
112 | respError = ParseResponseToBaseModel(resp, output, xmlResult, obsClient.conf.signature == SignatureObs)
113 | if respError != nil {
114 | doLog(LEVEL_WARN, "Parse response to BaseModel with error: %v", respError)
115 | }
116 | } else {
117 | doLog(LEVEL_WARN, "Do http request with error: %v", respError)
118 | }
119 |
120 | if isDebugLogEnabled() {
121 | doLog(LEVEL_DEBUG, "End method %s, obsclient cost %d ms", action, (GetCurrentTimestamp() - start))
122 | }
123 |
124 | return respError
125 | }
126 |
127 | func (obsClient ObsClient) doHttpGet(bucketName, objectKey string, params map[string]string,
128 | headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
129 | return obsClient.doHttp(HTTP_GET, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
130 | }
131 |
132 | func (obsClient ObsClient) doHttpHead(bucketName, objectKey string, params map[string]string,
133 | headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
134 | return obsClient.doHttp(HTTP_HEAD, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
135 | }
136 |
137 | func (obsClient ObsClient) doHttpOptions(bucketName, objectKey string, params map[string]string,
138 | headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
139 | return obsClient.doHttp(HTTP_OPTIONS, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
140 | }
141 |
142 | func (obsClient ObsClient) doHttpDelete(bucketName, objectKey string, params map[string]string,
143 | headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
144 | return obsClient.doHttp(HTTP_DELETE, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
145 | }
146 |
147 | func (obsClient ObsClient) doHttpPut(bucketName, objectKey string, params map[string]string,
148 | headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
149 | return obsClient.doHttp(HTTP_PUT, bucketName, objectKey, params, prepareHeaders(headers, true, obsClient.conf.signature == SignatureObs), data, repeatable)
150 | }
151 |
152 | func (obsClient ObsClient) doHttpPost(bucketName, objectKey string, params map[string]string,
153 | headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
154 | return obsClient.doHttp(HTTP_POST, bucketName, objectKey, params, prepareHeaders(headers, true, obsClient.conf.signature == SignatureObs), data, repeatable)
155 | }
156 |
157 | func (obsClient ObsClient) doHttpWithSignedUrl(action, method string, signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader, output IBaseModel, xmlResult bool) (respError error) {
158 | req, err := http.NewRequest(method, signedUrl, data)
159 | if err != nil {
160 | return err
161 | }
162 | if obsClient.conf.ctx != nil {
163 | req = req.WithContext(obsClient.conf.ctx)
164 | }
165 | var resp *http.Response
166 |
167 | doLog(LEVEL_INFO, "Do %s with signedUrl %s...", action, signedUrl)
168 |
169 | req.Header = actualSignedRequestHeaders
170 | if value, ok := req.Header[HEADER_HOST_CAMEL]; ok {
171 | req.Host = value[0]
172 | delete(req.Header, HEADER_HOST_CAMEL)
173 | } else if value, ok := req.Header[HEADER_HOST]; ok {
174 | req.Host = value[0]
175 | delete(req.Header, HEADER_HOST)
176 | }
177 |
178 | if value, ok := req.Header[HEADER_CONTENT_LENGTH_CAMEL]; ok {
179 | req.ContentLength = StringToInt64(value[0], -1)
180 | delete(req.Header, HEADER_CONTENT_LENGTH_CAMEL)
181 | } else if value, ok := req.Header[HEADER_CONTENT_LENGTH]; ok {
182 | req.ContentLength = StringToInt64(value[0], -1)
183 | delete(req.Header, HEADER_CONTENT_LENGTH)
184 | }
185 |
186 | req.Header[HEADER_USER_AGENT_CAMEL] = []string{USER_AGENT}
187 | start := GetCurrentTimestamp()
188 | resp, err = obsClient.httpClient.Do(req)
189 | if isInfoLogEnabled() {
190 | doLog(LEVEL_INFO, "Do http request cost %d ms", (GetCurrentTimestamp() - start))
191 | }
192 |
193 | var msg interface{}
194 | if err != nil {
195 | respError = err
196 | resp = nil
197 | } else {
198 | doLog(LEVEL_DEBUG, "Response headers: %v", resp.Header)
199 | if resp.StatusCode >= 300 {
200 | respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
201 | msg = resp.Status
202 | resp = nil
203 | } else {
204 | if output != nil {
205 | respError = ParseResponseToBaseModel(resp, output, xmlResult, obsClient.conf.signature == SignatureObs)
206 | }
207 | if respError != nil {
208 | doLog(LEVEL_WARN, "Parse response to BaseModel with error: %v", respError)
209 | }
210 | }
211 | }
212 |
213 | if msg != nil {
214 | doLog(LEVEL_ERROR, "Failed to send request with reason:%v", msg)
215 | }
216 |
217 | if isDebugLogEnabled() {
218 | doLog(LEVEL_DEBUG, "End method %s, obsclient cost %d ms", action, (GetCurrentTimestamp() - start))
219 | }
220 |
221 | return
222 | }
223 |
224 | func (obsClient ObsClient) doHttp(method, bucketName, objectKey string, params map[string]string,
225 | headers map[string][]string, data interface{}, repeatable bool) (resp *http.Response, respError error) {
226 |
227 | bucketName = strings.TrimSpace(bucketName)
228 |
229 | method = strings.ToUpper(method)
230 |
231 | var redirectUrl string
232 | var requestUrl string
233 | maxRetryCount := obsClient.conf.maxRetryCount
234 |
235 | var _data io.Reader
236 | if data != nil {
237 | if dataStr, ok := data.(string); ok {
238 | doLog(LEVEL_DEBUG, "Do http request with string: %s", dataStr)
239 | headers["Content-Length"] = []string{IntToString(len(dataStr))}
240 | _data = strings.NewReader(dataStr)
241 | } else if dataByte, ok := data.([]byte); ok {
242 | doLog(LEVEL_DEBUG, "Do http request with byte array")
243 | headers["Content-Length"] = []string{IntToString(len(dataByte))}
244 | _data = bytes.NewReader(dataByte)
245 | } else if dataReader, ok := data.(io.Reader); ok {
246 | _data = dataReader
247 | } else {
248 | doLog(LEVEL_WARN, "Data is not a valid io.Reader")
249 | return nil, errors.New("Data is not a valid io.Reader")
250 | }
251 | }
252 |
253 | for i := 0; i <= maxRetryCount; i++ {
254 | if redirectUrl != "" {
255 | parsedRedirectUrl, err := url.Parse(redirectUrl)
256 | if err != nil {
257 | return nil, err
258 | }
259 | requestUrl, _ = obsClient.doAuth(method, bucketName, objectKey, params, headers, parsedRedirectUrl.Host)
260 | if parsedRequestUrl, _ := url.Parse(requestUrl); parsedRequestUrl.RawQuery != "" && parsedRedirectUrl.RawQuery == "" {
261 | redirectUrl += "?" + parsedRequestUrl.RawQuery
262 | }
263 | requestUrl = redirectUrl
264 | } else {
265 | var err error
266 | requestUrl, err = obsClient.doAuth(method, bucketName, objectKey, params, headers, "")
267 | if err != nil {
268 | return nil, err
269 | }
270 | }
271 |
272 | req, err := http.NewRequest(method, requestUrl, _data)
273 | if obsClient.conf.ctx != nil {
274 | req = req.WithContext(obsClient.conf.ctx)
275 | }
276 | if err != nil {
277 | return nil, err
278 | }
279 | doLog(LEVEL_DEBUG, "Do request with url [%s] and method [%s]", requestUrl, method)
280 |
281 | if isDebugLogEnabled() {
282 | auth := headers[HEADER_AUTH_CAMEL]
283 | delete(headers, HEADER_AUTH_CAMEL)
284 | doLog(LEVEL_DEBUG, "Request headers: %v", headers)
285 | headers[HEADER_AUTH_CAMEL] = auth
286 | }
287 |
288 | for key, value := range headers {
289 | if key == HEADER_HOST_CAMEL {
290 | req.Host = value[0]
291 | delete(headers, key)
292 | } else if key == HEADER_CONTENT_LENGTH_CAMEL {
293 | req.ContentLength = StringToInt64(value[0], -1)
294 | delete(headers, key)
295 | } else {
296 | req.Header[key] = value
297 | }
298 | }
299 |
300 | req.Header[HEADER_USER_AGENT_CAMEL] = []string{USER_AGENT}
301 |
302 | start := GetCurrentTimestamp()
303 | resp, err = obsClient.httpClient.Do(req)
304 | if isInfoLogEnabled() {
305 | doLog(LEVEL_INFO, "Do http request cost %d ms", (GetCurrentTimestamp() - start))
306 | }
307 |
308 | var msg interface{}
309 | if err != nil {
310 | msg = err
311 | respError = err
312 | resp = nil
313 | } else {
314 | doLog(LEVEL_DEBUG, "Response headers: %v", resp.Header)
315 | if resp.StatusCode < 300 {
316 | break
317 | } else if !repeatable || (resp.StatusCode >= 400 && resp.StatusCode < 500) || resp.StatusCode == 304 {
318 | respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
319 | resp = nil
320 | break
321 | } else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
322 | if location := resp.Header.Get(HEADER_LOCATION_CAMEL); location != "" {
323 | redirectUrl = location
324 | doLog(LEVEL_WARN, "Redirect request to %s", redirectUrl)
325 | msg = resp.Status
326 | maxRetryCount++
327 | } else {
328 | respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
329 | resp = nil
330 | break
331 | }
332 | } else {
333 | msg = resp.Status
334 | }
335 | }
336 | if i != maxRetryCount {
337 | if resp != nil {
338 | resp.Body.Close()
339 | resp = nil
340 | }
341 | if _, ok := headers[HEADER_AUTH_CAMEL]; ok {
342 | delete(headers, HEADER_AUTH_CAMEL)
343 | }
344 | doLog(LEVEL_WARN, "Failed to send request with reason:%v, will try again", msg)
345 | if r, ok := _data.(*strings.Reader); ok {
346 | r.Seek(0, 0)
347 | } else if r, ok := _data.(*bytes.Reader); ok {
348 | r.Seek(0, 0)
349 | } else if r, ok := _data.(*fileReaderWrapper); ok {
350 | fd, err := os.Open(r.filePath)
351 | if err != nil {
352 | return nil, err
353 | }
354 | defer fd.Close()
355 | fileReaderWrapper := &fileReaderWrapper{filePath: r.filePath}
356 | fileReaderWrapper.mark = r.mark
357 | fileReaderWrapper.reader = fd
358 | fileReaderWrapper.totalCount = r.totalCount
359 | _data = fileReaderWrapper
360 | fd.Seek(r.mark, 0)
361 | } else if r, ok := _data.(*readerWrapper); ok {
362 | r.seek(0, 0)
363 | }
364 | time.Sleep(time.Duration(float64(i+2) * rand.Float64() * float64(time.Second)))
365 | } else {
366 | doLog(LEVEL_ERROR, "Failed to send request with reason:%v", msg)
367 | if resp != nil {
368 | respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
369 | resp = nil
370 | }
371 | }
372 | }
373 | return
374 | }
375 |
376 | type connDelegate struct {
377 | conn net.Conn
378 | socketTimeout time.Duration
379 | finalTimeout time.Duration
380 | }
381 |
382 | func getConnDelegate(conn net.Conn, socketTimeout int, finalTimeout int) *connDelegate {
383 | return &connDelegate{
384 | conn: conn,
385 | socketTimeout: time.Second * time.Duration(socketTimeout),
386 | finalTimeout: time.Second * time.Duration(finalTimeout),
387 | }
388 | }
389 |
390 | func (delegate *connDelegate) Read(b []byte) (n int, err error) {
391 | delegate.SetReadDeadline(time.Now().Add(delegate.socketTimeout))
392 | n, err = delegate.conn.Read(b)
393 | delegate.SetReadDeadline(time.Now().Add(delegate.finalTimeout))
394 | return n, err
395 | }
396 |
397 | func (delegate *connDelegate) Write(b []byte) (n int, err error) {
398 | delegate.SetWriteDeadline(time.Now().Add(delegate.socketTimeout))
399 | n, err = delegate.conn.Write(b)
400 | finalTimeout := time.Now().Add(delegate.finalTimeout)
401 | delegate.SetWriteDeadline(finalTimeout)
402 | delegate.SetReadDeadline(finalTimeout)
403 | return n, err
404 | }
405 |
406 | func (delegate *connDelegate) Close() error {
407 | return delegate.conn.Close()
408 | }
409 |
410 | func (delegate *connDelegate) LocalAddr() net.Addr {
411 | return delegate.conn.LocalAddr()
412 | }
413 |
414 | func (delegate *connDelegate) RemoteAddr() net.Addr {
415 | return delegate.conn.RemoteAddr()
416 | }
417 |
418 | func (delegate *connDelegate) SetDeadline(t time.Time) error {
419 | return delegate.conn.SetDeadline(t)
420 | }
421 |
422 | func (delegate *connDelegate) SetReadDeadline(t time.Time) error {
423 | return delegate.conn.SetReadDeadline(t)
424 | }
425 |
426 | func (delegate *connDelegate) SetWriteDeadline(t time.Time) error {
427 | return delegate.conn.SetWriteDeadline(t)
428 | }
429 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
3 | github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
5 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
7 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
8 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
9 | github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
10 | github.com/aliyun/aliyun-oss-go-sdk v2.1.7+incompatible h1:hG4TUPxKksYy39lwrfuCYUxGtmfYwgi7OxbQInWfKMI=
11 | github.com/aliyun/aliyun-oss-go-sdk v2.1.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
12 | github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
13 | github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
14 | github.com/baidubce/bce-sdk-go v0.9.57 h1:eNZN6K8Lfuv/+fnrbWFY8P41+AvlVhf4ee272QwFlcw=
15 | github.com/baidubce/bce-sdk-go v0.9.57/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
16 | github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
17 | github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
18 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
19 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
20 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
21 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
22 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
23 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
24 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
25 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
26 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
27 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
28 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
29 | github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
30 | github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
31 | github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
32 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
33 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
36 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
37 | github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
38 | github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
39 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
40 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
41 | github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
42 | github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU=
43 | github.com/go-ini/ini v1.62.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
44 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
45 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
46 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
47 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
48 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
49 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
50 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
51 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
52 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
53 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
54 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
55 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
56 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
57 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
58 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
59 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
60 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
61 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
62 | github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
63 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
64 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
65 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
66 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
67 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
68 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
69 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
70 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
71 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
72 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
73 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
74 | github.com/gookit/color v1.3.6 h1:Rgbazd4JO5AgSTVGS3o0nvaSdwdrS8bzvIXwtK6OiMk=
75 | github.com/gookit/color v1.3.6/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
76 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
77 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
78 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
79 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
80 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
81 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
82 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
83 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
84 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
85 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
86 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
87 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
88 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
89 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
90 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
91 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
92 | github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
93 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
94 | github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
95 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
96 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
97 | github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
98 | github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
99 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
100 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
101 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
102 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
103 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
104 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
105 | github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
106 | github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
107 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
108 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
109 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
110 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
111 | github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
112 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
113 | github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
114 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
115 | github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
116 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
117 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
118 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
119 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
120 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
121 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
122 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
123 | github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
124 | github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
125 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
126 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
127 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
128 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
129 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
130 | github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
131 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
132 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
133 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
134 | github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
135 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
136 | github.com/qiniu/api.v7/v7 v7.8.2 h1:f08kI0MmsJNzK4sUS8bG3HDH67ktwd/ji23Gkiy2ra4=
137 | github.com/qiniu/api.v7/v7 v7.8.2/go.mod h1:FPsIqxh1Ym3X01sANE5ZwXfLZSWoCUp5+jNI8cLo3l0=
138 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
139 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
140 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
141 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
142 | github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
143 | github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
144 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
145 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
146 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
147 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
148 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
149 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
150 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
151 | github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
152 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
153 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
154 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
155 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
156 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
157 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
158 | github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
159 | github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
160 | github.com/tencentyun/cos-go-sdk-v5 v0.7.24 h1:ZsZij764lOaPsj7mEAlyxXvslGt6/m312Tzqj/zeRpo=
161 | github.com/tencentyun/cos-go-sdk-v5 v0.7.24/go.mod h1:wQBO5HdAkLjj2q6XQiIfDSP8DXDNrppDRw2Kp/1BODA=
162 | github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
163 | github.com/upyun/go-sdk v2.1.0+incompatible h1:OdjXghQ/TVetWV16Pz3C1/SUpjhGBVPr+cLiqZLLyq0=
164 | github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNWRwfj0zpJ9J626HEqs=
165 | github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
166 | github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
167 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
168 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
169 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
170 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
171 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
172 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
173 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
174 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
175 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
176 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
177 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
178 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
179 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
180 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
181 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
182 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
183 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
184 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
185 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
186 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
187 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
188 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
189 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
190 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
191 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
192 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
193 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
194 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
195 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
196 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
197 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
198 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
199 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
200 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
201 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
202 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
203 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
204 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
205 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
206 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
207 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
208 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
209 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
210 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
211 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
212 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
213 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
214 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
215 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
216 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
217 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
218 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
219 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
220 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
221 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
222 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
223 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
224 |
--------------------------------------------------------------------------------
/obs/temporary.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "os"
9 | "strings"
10 | "time"
11 | )
12 |
13 | func (obsClient ObsClient) CreateSignedUrl(input *CreateSignedUrlInput) (output *CreateSignedUrlOutput, err error) {
14 | if input == nil {
15 | return nil, errors.New("CreateSignedUrlInput is nil")
16 | }
17 |
18 | params := make(map[string]string, len(input.QueryParams))
19 | for key, value := range input.QueryParams {
20 | params[key] = value
21 | }
22 |
23 | if input.SubResource != "" {
24 | params[string(input.SubResource)] = ""
25 | }
26 |
27 | headers := make(map[string][]string, len(input.Headers))
28 | for key, value := range input.Headers {
29 | headers[key] = []string{value}
30 | }
31 |
32 | if input.Expires <= 0 {
33 | input.Expires = 300
34 | }
35 |
36 | requestUrl, err := obsClient.doAuthTemporary(string(input.Method), input.Bucket, input.Key, params, headers, int64(input.Expires))
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | output = &CreateSignedUrlOutput{
42 | SignedUrl: requestUrl,
43 | ActualSignedRequestHeaders: headers,
44 | }
45 | return
46 | }
47 |
48 | func (obsClient ObsClient) CreateBrowserBasedSignature(input *CreateBrowserBasedSignatureInput) (output *CreateBrowserBasedSignatureOutput, err error) {
49 | if input == nil {
50 | return nil, errors.New("CreateBrowserBasedSignatureInput is nil")
51 | }
52 |
53 | params := make(map[string]string, len(input.FormParams))
54 | for key, value := range input.FormParams {
55 | params[key] = value
56 | }
57 |
58 | date := time.Now().UTC()
59 | shortDate := date.Format(SHORT_DATE_FORMAT)
60 | longDate := date.Format(LONG_DATE_FORMAT)
61 |
62 | credential, _ := getCredential(obsClient.conf.securityProvider.ak, obsClient.conf.region, shortDate)
63 |
64 | if input.Expires <= 0 {
65 | input.Expires = 300
66 | }
67 |
68 | expiration := date.Add(time.Second * time.Duration(input.Expires)).Format(ISO8601_DATE_FORMAT)
69 | params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
70 | params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
71 | params[PARAM_DATE_AMZ_CAMEL] = longDate
72 |
73 | if obsClient.conf.securityProvider.securityToken != "" {
74 | params[HEADER_STS_TOKEN_AMZ] = obsClient.conf.securityProvider.securityToken
75 | }
76 |
77 | matchAnyBucket := true
78 | matchAnyKey := true
79 | count := 5
80 | if bucket := strings.TrimSpace(input.Bucket); bucket != "" {
81 | params["bucket"] = bucket
82 | matchAnyBucket = false
83 | count--
84 | }
85 |
86 | if key := strings.TrimSpace(input.Key); key != "" {
87 | params["key"] = key
88 | matchAnyKey = false
89 | count--
90 | }
91 |
92 | originPolicySlice := make([]string, 0, len(params)+count)
93 | originPolicySlice = append(originPolicySlice, fmt.Sprintf("{\"expiration\":\"%s\",", expiration))
94 | originPolicySlice = append(originPolicySlice, "\"conditions\":[")
95 | for key, value := range params {
96 | if _key := strings.TrimSpace(strings.ToLower(key)); _key != "" {
97 | originPolicySlice = append(originPolicySlice, fmt.Sprintf("{\"%s\":\"%s\"},", _key, value))
98 | }
99 | }
100 |
101 | if matchAnyBucket {
102 | originPolicySlice = append(originPolicySlice, "[\"starts-with\", \"$bucket\", \"\"],")
103 | }
104 |
105 | if matchAnyKey {
106 | originPolicySlice = append(originPolicySlice, "[\"starts-with\", \"$key\", \"\"],")
107 | }
108 |
109 | originPolicySlice = append(originPolicySlice, "]}")
110 |
111 | originPolicy := strings.Join(originPolicySlice, "")
112 | policy := Base64Encode([]byte(originPolicy))
113 | signature := getSignature(policy, obsClient.conf.securityProvider.sk, obsClient.conf.region, shortDate)
114 |
115 | output = &CreateBrowserBasedSignatureOutput{
116 | OriginPolicy: originPolicy,
117 | Policy: policy,
118 | Algorithm: params[PARAM_ALGORITHM_AMZ_CAMEL],
119 | Credential: params[PARAM_CREDENTIAL_AMZ_CAMEL],
120 | Date: params[PARAM_DATE_AMZ_CAMEL],
121 | Signature: signature,
122 | }
123 | return
124 | }
125 |
126 | func (obsClient ObsClient) ListBucketsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListBucketsOutput, err error) {
127 | output = &ListBucketsOutput{}
128 | err = obsClient.doHttpWithSignedUrl("ListBuckets", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
129 | if err != nil {
130 | output = nil
131 | }
132 | return
133 | }
134 |
135 | func (obsClient ObsClient) CreateBucketWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
136 | output = &BaseModel{}
137 | err = obsClient.doHttpWithSignedUrl("CreateBucket", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
138 | if err != nil {
139 | output = nil
140 | }
141 | return
142 | }
143 |
144 | func (obsClient ObsClient) DeleteBucketWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
145 | output = &BaseModel{}
146 | err = obsClient.doHttpWithSignedUrl("DeleteBucket", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
147 | if err != nil {
148 | output = nil
149 | }
150 | return
151 | }
152 |
153 | func (obsClient ObsClient) SetBucketStoragePolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
154 | output = &BaseModel{}
155 | err = obsClient.doHttpWithSignedUrl("SetBucketStoragePolicy", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
156 | if err != nil {
157 | output = nil
158 | }
159 | return
160 | }
161 |
162 | func (obsClient ObsClient) GetBucketStoragePolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketStoragePolicyOutput, err error) {
163 | output = &GetBucketStoragePolicyOutput{}
164 | err = obsClient.doHttpWithSignedUrl("GetBucketStoragePolicy", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
165 | if err != nil {
166 | output = nil
167 | }
168 | return
169 | }
170 |
171 | func (obsClient ObsClient) ListObjectsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListObjectsOutput, err error) {
172 | output = &ListObjectsOutput{}
173 | err = obsClient.doHttpWithSignedUrl("ListObjects", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
174 | if err != nil {
175 | output = nil
176 | } else {
177 | if location, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
178 | output.Location = location[0]
179 | }
180 | }
181 | return
182 | }
183 |
184 | func (obsClient ObsClient) ListVersionsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListVersionsOutput, err error) {
185 | output = &ListVersionsOutput{}
186 | err = obsClient.doHttpWithSignedUrl("ListVersions", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
187 | if err != nil {
188 | output = nil
189 | } else {
190 | if location, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
191 | output.Location = location[0]
192 | }
193 | }
194 | return
195 | }
196 |
197 | func (obsClient ObsClient) ListMultipartUploadsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListMultipartUploadsOutput, err error) {
198 | output = &ListMultipartUploadsOutput{}
199 | err = obsClient.doHttpWithSignedUrl("ListMultipartUploads", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
200 | if err != nil {
201 | output = nil
202 | }
203 | return
204 | }
205 |
206 | func (obsClient ObsClient) SetBucketQuotaWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
207 | output = &BaseModel{}
208 | err = obsClient.doHttpWithSignedUrl("SetBucketQuota", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
209 | if err != nil {
210 | output = nil
211 | }
212 | return
213 | }
214 |
215 | func (obsClient ObsClient) GetBucketQuotaWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketQuotaOutput, err error) {
216 | output = &GetBucketQuotaOutput{}
217 | err = obsClient.doHttpWithSignedUrl("GetBucketQuota", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
218 | if err != nil {
219 | output = nil
220 | }
221 | return
222 | }
223 |
224 | func (obsClient ObsClient) HeadBucketWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
225 | output = &BaseModel{}
226 | err = obsClient.doHttpWithSignedUrl("HeadBucket", HTTP_HEAD, signedUrl, actualSignedRequestHeaders, nil, output, true)
227 | if err != nil {
228 | output = nil
229 | }
230 | return
231 | }
232 |
233 | func (obsClient ObsClient) GetBucketMetadataWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketMetadataOutput, err error) {
234 | output = &GetBucketMetadataOutput{}
235 | err = obsClient.doHttpWithSignedUrl("GetBucketMetadata", HTTP_HEAD, signedUrl, actualSignedRequestHeaders, nil, output, true)
236 | if err != nil {
237 | output = nil
238 | } else {
239 | ParseGetBucketMetadataOutput(output)
240 | }
241 | return
242 | }
243 |
244 | func (obsClient ObsClient) GetBucketStorageInfoWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketStorageInfoOutput, err error) {
245 | output = &GetBucketStorageInfoOutput{}
246 | err = obsClient.doHttpWithSignedUrl("GetBucketStorageInfo", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
247 | if err != nil {
248 | output = nil
249 | }
250 | return
251 | }
252 |
253 | func (obsClient ObsClient) GetBucketLocationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketLocationOutput, err error) {
254 | output = &GetBucketLocationOutput{}
255 | err = obsClient.doHttpWithSignedUrl("GetBucketLocation", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
256 | if err != nil {
257 | output = nil
258 | }
259 | return
260 | }
261 |
262 | func (obsClient ObsClient) SetBucketAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
263 | output = &BaseModel{}
264 | err = obsClient.doHttpWithSignedUrl("SetBucketAcl", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
265 | if err != nil {
266 | output = nil
267 | }
268 | return
269 | }
270 |
271 | func (obsClient ObsClient) GetBucketAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketAclOutput, err error) {
272 | output = &GetBucketAclOutput{}
273 | err = obsClient.doHttpWithSignedUrl("GetBucketAcl", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
274 | if err != nil {
275 | output = nil
276 | }
277 | return
278 | }
279 |
280 | func (obsClient ObsClient) SetBucketPolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
281 | output = &BaseModel{}
282 | err = obsClient.doHttpWithSignedUrl("SetBucketPolicy", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
283 | if err != nil {
284 | output = nil
285 | }
286 | return
287 | }
288 |
289 | func (obsClient ObsClient) GetBucketPolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketPolicyOutput, err error) {
290 | output = &GetBucketPolicyOutput{}
291 | err = obsClient.doHttpWithSignedUrl("GetBucketPolicy", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, false)
292 | if err != nil {
293 | output = nil
294 | }
295 | return
296 | }
297 |
298 | func (obsClient ObsClient) DeleteBucketPolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
299 | output = &BaseModel{}
300 | err = obsClient.doHttpWithSignedUrl("DeleteBucketPolicy", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
301 | if err != nil {
302 | output = nil
303 | }
304 | return
305 | }
306 |
307 | func (obsClient ObsClient) SetBucketCorsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
308 | output = &BaseModel{}
309 | err = obsClient.doHttpWithSignedUrl("SetBucketCors", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
310 | if err != nil {
311 | output = nil
312 | }
313 | return
314 | }
315 |
316 | func (obsClient ObsClient) GetBucketCorsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketCorsOutput, err error) {
317 | output = &GetBucketCorsOutput{}
318 | err = obsClient.doHttpWithSignedUrl("GetBucketCors", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
319 | if err != nil {
320 | output = nil
321 | }
322 | return
323 | }
324 |
325 | func (obsClient ObsClient) DeleteBucketCorsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
326 | output = &BaseModel{}
327 | err = obsClient.doHttpWithSignedUrl("DeleteBucketCors", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
328 | if err != nil {
329 | output = nil
330 | }
331 | return
332 | }
333 |
334 | func (obsClient ObsClient) SetBucketVersioningWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
335 | output = &BaseModel{}
336 | err = obsClient.doHttpWithSignedUrl("SetBucketVersioning", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
337 | if err != nil {
338 | output = nil
339 | }
340 | return
341 | }
342 |
343 | func (obsClient ObsClient) GetBucketVersioningWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketVersioningOutput, err error) {
344 | output = &GetBucketVersioningOutput{}
345 | err = obsClient.doHttpWithSignedUrl("GetBucketVersioning", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
346 | if err != nil {
347 | output = nil
348 | }
349 | return
350 | }
351 |
352 | func (obsClient ObsClient) SetBucketWebsiteConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
353 | output = &BaseModel{}
354 | err = obsClient.doHttpWithSignedUrl("SetBucketWebsiteConfiguration", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
355 | if err != nil {
356 | output = nil
357 | }
358 | return
359 | }
360 |
361 | func (obsClient ObsClient) GetBucketWebsiteConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketWebsiteConfigurationOutput, err error) {
362 | output = &GetBucketWebsiteConfigurationOutput{}
363 | err = obsClient.doHttpWithSignedUrl("GetBucketWebsiteConfiguration", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
364 | if err != nil {
365 | output = nil
366 | }
367 | return
368 | }
369 |
370 | func (obsClient ObsClient) DeleteBucketWebsiteConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
371 | output = &BaseModel{}
372 | err = obsClient.doHttpWithSignedUrl("DeleteBucketWebsiteConfiguration", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
373 | if err != nil {
374 | output = nil
375 | }
376 | return
377 | }
378 |
379 | func (obsClient ObsClient) SetBucketLoggingConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
380 | output = &BaseModel{}
381 | err = obsClient.doHttpWithSignedUrl("SetBucketLoggingConfiguration", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
382 | if err != nil {
383 | output = nil
384 | }
385 | return
386 | }
387 |
388 | func (obsClient ObsClient) GetBucketLoggingConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketLoggingConfigurationOutput, err error) {
389 | output = &GetBucketLoggingConfigurationOutput{}
390 | err = obsClient.doHttpWithSignedUrl("GetBucketLoggingConfiguration", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
391 | if err != nil {
392 | output = nil
393 | }
394 | return
395 | }
396 |
397 | func (obsClient ObsClient) SetBucketLifecycleConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
398 | output = &BaseModel{}
399 | err = obsClient.doHttpWithSignedUrl("SetBucketLifecycleConfiguration", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
400 | if err != nil {
401 | output = nil
402 | }
403 | return
404 | }
405 |
406 | func (obsClient ObsClient) GetBucketLifecycleConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketLifecycleConfigurationOutput, err error) {
407 | output = &GetBucketLifecycleConfigurationOutput{}
408 | err = obsClient.doHttpWithSignedUrl("GetBucketLifecycleConfiguration", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
409 | if err != nil {
410 | output = nil
411 | }
412 | return
413 | }
414 |
415 | func (obsClient ObsClient) DeleteBucketLifecycleConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
416 | output = &BaseModel{}
417 | err = obsClient.doHttpWithSignedUrl("DeleteBucketLifecycleConfiguration", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
418 | if err != nil {
419 | output = nil
420 | }
421 | return
422 | }
423 |
424 | func (obsClient ObsClient) SetBucketTaggingWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
425 | output = &BaseModel{}
426 | err = obsClient.doHttpWithSignedUrl("SetBucketTagging", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
427 | if err != nil {
428 | output = nil
429 | }
430 | return
431 | }
432 |
433 | func (obsClient ObsClient) GetBucketTaggingWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketTaggingOutput, err error) {
434 | output = &GetBucketTaggingOutput{}
435 | err = obsClient.doHttpWithSignedUrl("GetBucketTagging", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
436 | if err != nil {
437 | output = nil
438 | }
439 | return
440 | }
441 |
442 | func (obsClient ObsClient) DeleteBucketTaggingWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
443 | output = &BaseModel{}
444 | err = obsClient.doHttpWithSignedUrl("DeleteBucketTagging", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
445 | if err != nil {
446 | output = nil
447 | }
448 | return
449 | }
450 |
451 | func (obsClient ObsClient) SetBucketNotificationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
452 | output = &BaseModel{}
453 | err = obsClient.doHttpWithSignedUrl("SetBucketNotification", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
454 | if err != nil {
455 | output = nil
456 | }
457 | return
458 | }
459 |
460 | func (obsClient ObsClient) GetBucketNotificationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketNotificationOutput, err error) {
461 | output = &GetBucketNotificationOutput{}
462 | err = obsClient.doHttpWithSignedUrl("GetBucketNotification", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
463 | if err != nil {
464 | output = nil
465 | }
466 | return
467 | }
468 |
469 | func (obsClient ObsClient) DeleteObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *DeleteObjectOutput, err error) {
470 | output = &DeleteObjectOutput{}
471 | err = obsClient.doHttpWithSignedUrl("DeleteObject", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
472 | if err != nil {
473 | output = nil
474 | } else {
475 | ParseDeleteObjectOutput(output)
476 | }
477 | return
478 | }
479 |
480 | func (obsClient ObsClient) DeleteObjectsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *DeleteObjectsOutput, err error) {
481 | output = &DeleteObjectsOutput{}
482 | err = obsClient.doHttpWithSignedUrl("DeleteObjects", HTTP_POST, signedUrl, actualSignedRequestHeaders, data, output, true)
483 | if err != nil {
484 | output = nil
485 | }
486 | return
487 | }
488 |
489 | func (obsClient ObsClient) SetObjectAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
490 | output = &BaseModel{}
491 | err = obsClient.doHttpWithSignedUrl("SetObjectAcl", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
492 | if err != nil {
493 | output = nil
494 | }
495 | return
496 | }
497 |
498 | func (obsClient ObsClient) GetObjectAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetObjectAclOutput, err error) {
499 | output = &GetObjectAclOutput{}
500 | err = obsClient.doHttpWithSignedUrl("GetObjectAcl", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
501 | if err != nil {
502 | output = nil
503 | } else {
504 | if versionId, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
505 | output.VersionId = versionId[0]
506 | }
507 | }
508 | return
509 | }
510 |
511 | func (obsClient ObsClient) RestoreObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
512 | output = &BaseModel{}
513 | err = obsClient.doHttpWithSignedUrl("RestoreObject", HTTP_POST, signedUrl, actualSignedRequestHeaders, data, output, true)
514 | if err != nil {
515 | output = nil
516 | }
517 | return
518 | }
519 |
520 | func (obsClient ObsClient) GetObjectMetadataWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetObjectMetadataOutput, err error) {
521 | output = &GetObjectMetadataOutput{}
522 | err = obsClient.doHttpWithSignedUrl("GetObjectMetadata", HTTP_HEAD, signedUrl, actualSignedRequestHeaders, nil, output, true)
523 | if err != nil {
524 | output = nil
525 | } else {
526 | ParseGetObjectMetadataOutput(output)
527 | }
528 | return
529 | }
530 |
531 | func (obsClient ObsClient) GetObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetObjectOutput, err error) {
532 | output = &GetObjectOutput{}
533 | err = obsClient.doHttpWithSignedUrl("GetObject", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
534 | if err != nil {
535 | output = nil
536 | } else {
537 | ParseGetObjectOutput(output)
538 | }
539 | return
540 | }
541 |
542 | func (obsClient ObsClient) PutObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *PutObjectOutput, err error) {
543 | output = &PutObjectOutput{}
544 | err = obsClient.doHttpWithSignedUrl("PutObject", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
545 | if err != nil {
546 | output = nil
547 | } else {
548 | ParsePutObjectOutput(output)
549 | }
550 | return
551 | }
552 |
553 | func (obsClient ObsClient) PutFileWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, sourceFile string) (output *PutObjectOutput, err error) {
554 | var data io.Reader
555 | sourceFile = strings.TrimSpace(sourceFile)
556 | if sourceFile != "" {
557 | fd, err := os.Open(sourceFile)
558 | if err != nil {
559 | return nil, err
560 | }
561 | defer fd.Close()
562 |
563 | stat, err := fd.Stat()
564 | if err != nil {
565 | return nil, err
566 | }
567 | fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile}
568 | fileReaderWrapper.reader = fd
569 |
570 | var contentLength int64
571 | if value, ok := actualSignedRequestHeaders[HEADER_CONTENT_LENGTH_CAMEL]; ok {
572 | contentLength = StringToInt64(value[0], -1)
573 | } else if value, ok := actualSignedRequestHeaders[HEADER_CONTENT_LENGTH]; ok {
574 | contentLength = StringToInt64(value[0], -1)
575 | } else {
576 | contentLength = stat.Size()
577 | }
578 | if contentLength > stat.Size() {
579 | return nil, errors.New("ContentLength is larger than fileSize")
580 | }
581 | fileReaderWrapper.totalCount = contentLength
582 | data = fileReaderWrapper
583 | }
584 |
585 | output = &PutObjectOutput{}
586 | err = obsClient.doHttpWithSignedUrl("PutObject", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
587 | if err != nil {
588 | output = nil
589 | } else {
590 | ParsePutObjectOutput(output)
591 | }
592 | return
593 | }
594 |
595 | func (obsClient ObsClient) CopyObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *CopyObjectOutput, err error) {
596 | output = &CopyObjectOutput{}
597 | err = obsClient.doHttpWithSignedUrl("CopyObject", HTTP_PUT, signedUrl, actualSignedRequestHeaders, nil, output, true)
598 | if err != nil {
599 | output = nil
600 | } else {
601 | ParseCopyObjectOutput(output)
602 | }
603 | return
604 | }
605 |
606 | func (obsClient ObsClient) AbortMultipartUploadWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
607 | output = &BaseModel{}
608 | err = obsClient.doHttpWithSignedUrl("AbortMultipartUpload", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
609 | if err != nil {
610 | output = nil
611 | }
612 | return
613 | }
614 |
615 | func (obsClient ObsClient) InitiateMultipartUploadWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *InitiateMultipartUploadOutput, err error) {
616 | output = &InitiateMultipartUploadOutput{}
617 | err = obsClient.doHttpWithSignedUrl("InitiateMultipartUpload", HTTP_POST, signedUrl, actualSignedRequestHeaders, nil, output, true)
618 | if err != nil {
619 | output = nil
620 | } else {
621 | ParseInitiateMultipartUploadOutput(output)
622 | }
623 | return
624 | }
625 |
626 | func (obsClient ObsClient) UploadPartWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *UploadPartOutput, err error) {
627 | output = &UploadPartOutput{}
628 | err = obsClient.doHttpWithSignedUrl("UploadPart", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
629 | if err != nil {
630 | output = nil
631 | } else {
632 | ParseUploadPartOutput(output)
633 | }
634 | return
635 | }
636 |
637 | func (obsClient ObsClient) CompleteMultipartUploadWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *CompleteMultipartUploadOutput, err error) {
638 | output = &CompleteMultipartUploadOutput{}
639 | err = obsClient.doHttpWithSignedUrl("CompleteMultipartUpload", HTTP_POST, signedUrl, actualSignedRequestHeaders, data, output, true)
640 | if err != nil {
641 | output = nil
642 | } else {
643 | ParseCompleteMultipartUploadOutput(output)
644 | }
645 | return
646 | }
647 |
648 | func (obsClient ObsClient) ListPartsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListPartsOutput, err error) {
649 | output = &ListPartsOutput{}
650 | err = obsClient.doHttpWithSignedUrl("ListParts", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
651 | if err != nil {
652 | output = nil
653 | }
654 | return
655 | }
656 |
657 | func (obsClient ObsClient) CopyPartWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *CopyPartOutput, err error) {
658 | output = &CopyPartOutput{}
659 | err = obsClient.doHttpWithSignedUrl("CopyPart", HTTP_PUT, signedUrl, actualSignedRequestHeaders, nil, output, true)
660 | if err != nil {
661 | output = nil
662 | } else {
663 | ParseCopyPartOutput(output)
664 | }
665 | return
666 | }
667 |
--------------------------------------------------------------------------------
/obs/convert.go:
--------------------------------------------------------------------------------
1 | package obs
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "net/http"
9 | "reflect"
10 | "strings"
11 | "time"
12 | )
13 |
14 | func cleanHeaderPrefix(header http.Header, isObs bool) map[string][]string {
15 | responseHeaders := make(map[string][]string)
16 | for key, value := range header {
17 | if len(value) > 0 {
18 | key = strings.ToLower(key)
19 | headerPrefix := HEADER_PREFIX
20 | if isObs {
21 | headerPrefix = HEADER_PREFIX_OBS
22 | }
23 | if strings.HasPrefix(key, headerPrefix) {
24 | key = key[len(headerPrefix):]
25 | }
26 | responseHeaders[key] = value
27 | }
28 | }
29 | return responseHeaders
30 | }
31 |
32 | func ParseStringToEventType(value string) (ret EventType) {
33 | switch value {
34 | case "ObjectCreated:*", "s3:ObjectCreated:*":
35 | ret = ObjectCreatedAll
36 | case "ObjectCreated:Put", "s3:ObjectCreated:Put":
37 | ret = ObjectCreatedPut
38 | case "ObjectCreated:Post", "s3:ObjectCreated:Post":
39 | ret = ObjectCreatedPost
40 | case "ObjectCreated:Copy", "s3:ObjectCreated:Copy":
41 | ret = ObjectCreatedCopy
42 | case "ObjectCreated:CompleteMultipartUpload", "s3:ObjectCreated:CompleteMultipartUpload":
43 | ret = ObjectCreatedCompleteMultipartUpload
44 | case "ObjectRemoved:*", "s3:ObjectRemoved:*":
45 | ret = ObjectRemovedAll
46 | case "ObjectRemoved:Delete", "s3:ObjectRemoved:Delete":
47 | ret = ObjectRemovedDelete
48 | case "ObjectRemoved:DeleteMarkerCreated", "s3:ObjectRemoved:DeleteMarkerCreated":
49 | ret = ObjectRemovedDeleteMarkerCreated
50 | default:
51 | ret = ""
52 | }
53 | return
54 | }
55 |
56 | func ParseStringToStorageClassType(value string) (ret StorageClassType) {
57 | switch value {
58 | case "STANDARD":
59 | ret = StorageClassStandard
60 | case "STANDARD_IA", "WARM":
61 | ret = StorageClassWarm
62 | case "GLACIER", "COLD":
63 | ret = StorageClassCold
64 | default:
65 | ret = ""
66 | }
67 | return
68 | }
69 |
70 | func convertGrantToXml(grant Grant, isObs bool) string {
71 | xml := make([]string, 0, 4)
72 | if !isObs {
73 | xml = append(xml, fmt.Sprintf("", grant.Grantee.Type))
74 | } else {
75 | xml = append(xml, fmt.Sprintf(""))
76 | }
77 | if grant.Grantee.Type == GranteeUser {
78 | if grant.Grantee.ID != "" {
79 | granteeID := XmlTranscoding(grant.Grantee.ID)
80 | xml = append(xml, fmt.Sprintf("%s", granteeID))
81 | }
82 | if grant.Grantee.DisplayName != "" {
83 | granteeDisplayName := XmlTranscoding(grant.Grantee.DisplayName)
84 | xml = append(xml, fmt.Sprintf("%s", granteeDisplayName))
85 | }
86 | } else {
87 | if !isObs {
88 | if grant.Grantee.URI == GroupAllUsers || grant.Grantee.URI == GroupAuthenticatedUsers {
89 | xml = append(xml, fmt.Sprintf("%s%s", "http://acs.amazonaws.com/groups/global/", grant.Grantee.URI))
90 | } else if grant.Grantee.URI == GroupLogDelivery {
91 | xml = append(xml, fmt.Sprintf("%s%s", "http://acs.amazonaws.com/groups/s3/", grant.Grantee.URI))
92 | } else {
93 | xml = append(xml, fmt.Sprintf("%s", grant.Grantee.URI))
94 | }
95 | } else if grant.Grantee.URI == GroupAllUsers {
96 | xml = append(xml, fmt.Sprintf("Everyone"))
97 | }
98 | }
99 | xml = append(xml, fmt.Sprintf(""))
100 | xml = append(xml, fmt.Sprintf("%s", grant.Permission))
101 | if isObs {
102 | xml = append(xml, fmt.Sprintf("%t", grant.Delivered))
103 | }
104 | xml = append(xml, fmt.Sprintf(""))
105 | return strings.Join(xml, "")
106 | }
107 |
108 | func ConvertLoggingStatusToXml(input BucketLoggingStatus, returnMd5 bool, isObs bool) (data string, md5 string) {
109 | grantsLength := len(input.TargetGrants)
110 | xml := make([]string, 0, 8+grantsLength)
111 |
112 | xml = append(xml, "")
113 | if input.Agency != "" {
114 | agency := XmlTranscoding(input.Agency)
115 | xml = append(xml, fmt.Sprintf("%s", agency))
116 | }
117 | if input.TargetBucket != "" || input.TargetPrefix != "" {
118 | xml = append(xml, "")
119 | xml = append(xml, fmt.Sprintf("%s", input.TargetBucket))
120 | targetPrefix := XmlTranscoding(input.TargetPrefix)
121 | xml = append(xml, fmt.Sprintf("%s", targetPrefix))
122 |
123 | if grantsLength > 0 {
124 | xml = append(xml, "")
125 | for _, grant := range input.TargetGrants {
126 | xml = append(xml, convertGrantToXml(grant, isObs))
127 | }
128 | xml = append(xml, "")
129 | }
130 |
131 | xml = append(xml, "")
132 | }
133 | xml = append(xml, "")
134 | data = strings.Join(xml, "")
135 | if returnMd5 {
136 | md5 = Base64Md5([]byte(data))
137 | }
138 | return
139 | }
140 |
141 | func ConvertAclToXml(input AccessControlPolicy, returnMd5 bool, isObs bool) (data string, md5 string) {
142 | xml := make([]string, 0, 4+len(input.Grants))
143 | ownerID := XmlTranscoding(input.Owner.ID)
144 | xml = append(xml, fmt.Sprintf("%s", ownerID))
145 | if input.Owner.DisplayName != "" {
146 | ownerDisplayName := XmlTranscoding(input.Owner.DisplayName)
147 | xml = append(xml, fmt.Sprintf("%s", ownerDisplayName))
148 | }
149 | xml = append(xml, "")
150 | for _, grant := range input.Grants {
151 | xml = append(xml, convertGrantToXml(grant, isObs))
152 | }
153 | xml = append(xml, "")
154 | data = strings.Join(xml, "")
155 | if returnMd5 {
156 | md5 = Base64Md5([]byte(data))
157 | }
158 | return
159 | }
160 |
161 | func convertConditionToXml(condition Condition) string {
162 | xml := make([]string, 0, 2)
163 | if condition.KeyPrefixEquals != "" {
164 | keyPrefixEquals := XmlTranscoding(condition.KeyPrefixEquals)
165 | xml = append(xml, fmt.Sprintf("%s", keyPrefixEquals))
166 | }
167 | if condition.HttpErrorCodeReturnedEquals != "" {
168 | xml = append(xml, fmt.Sprintf("%s", condition.HttpErrorCodeReturnedEquals))
169 | }
170 | if len(xml) > 0 {
171 | return fmt.Sprintf("%s", strings.Join(xml, ""))
172 | }
173 | return ""
174 | }
175 |
176 | func ConvertWebsiteConfigurationToXml(input BucketWebsiteConfiguration, returnMd5 bool) (data string, md5 string) {
177 | routingRuleLength := len(input.RoutingRules)
178 | xml := make([]string, 0, 6+routingRuleLength*10)
179 | xml = append(xml, "")
180 |
181 | if input.RedirectAllRequestsTo.HostName != "" {
182 | xml = append(xml, fmt.Sprintf("%s", input.RedirectAllRequestsTo.HostName))
183 | if input.RedirectAllRequestsTo.Protocol != "" {
184 | xml = append(xml, fmt.Sprintf("%s", input.RedirectAllRequestsTo.Protocol))
185 | }
186 | xml = append(xml, "")
187 | } else {
188 | indexDocumentSuffix := XmlTranscoding(input.IndexDocument.Suffix)
189 | xml = append(xml, fmt.Sprintf("%s", indexDocumentSuffix))
190 | if input.ErrorDocument.Key != "" {
191 | errorDocumentKey := XmlTranscoding(input.ErrorDocument.Key)
192 | xml = append(xml, fmt.Sprintf("%s", errorDocumentKey))
193 | }
194 | if routingRuleLength > 0 {
195 | xml = append(xml, "")
196 | for _, routingRule := range input.RoutingRules {
197 | xml = append(xml, "")
198 | xml = append(xml, "")
199 | if routingRule.Redirect.Protocol != "" {
200 | xml = append(xml, fmt.Sprintf("%s", routingRule.Redirect.Protocol))
201 | }
202 | if routingRule.Redirect.HostName != "" {
203 | xml = append(xml, fmt.Sprintf("%s", routingRule.Redirect.HostName))
204 | }
205 | if routingRule.Redirect.ReplaceKeyPrefixWith != "" {
206 | replaceKeyPrefixWith := XmlTranscoding(routingRule.Redirect.ReplaceKeyPrefixWith)
207 | xml = append(xml, fmt.Sprintf("%s", replaceKeyPrefixWith))
208 | }
209 |
210 | if routingRule.Redirect.ReplaceKeyWith != "" {
211 | replaceKeyWith := XmlTranscoding(routingRule.Redirect.ReplaceKeyWith)
212 | xml = append(xml, fmt.Sprintf("%s", replaceKeyWith))
213 | }
214 | if routingRule.Redirect.HttpRedirectCode != "" {
215 | xml = append(xml, fmt.Sprintf("%s", routingRule.Redirect.HttpRedirectCode))
216 | }
217 | xml = append(xml, "")
218 |
219 | if ret := convertConditionToXml(routingRule.Condition); ret != "" {
220 | xml = append(xml, ret)
221 | }
222 | xml = append(xml, "")
223 | }
224 | xml = append(xml, "")
225 | }
226 | }
227 |
228 | xml = append(xml, "")
229 | data = strings.Join(xml, "")
230 | if returnMd5 {
231 | md5 = Base64Md5([]byte(data))
232 | }
233 | return
234 | }
235 |
236 | func convertTransitionsToXml(transitions []Transition, isObs bool) string {
237 | if length := len(transitions); length > 0 {
238 | xml := make([]string, 0, length)
239 | for _, transition := range transitions {
240 | var temp string
241 | if transition.Days > 0 {
242 | temp = fmt.Sprintf("%d", transition.Days)
243 | } else if !transition.Date.IsZero() {
244 | temp = fmt.Sprintf("%s", transition.Date.UTC().Format(ISO8601_MIDNIGHT_DATE_FORMAT))
245 | }
246 | if temp != "" {
247 | if !isObs {
248 | storageClass := "STANDARD"
249 | if transition.StorageClass == "WARM" {
250 | storageClass = "STANDARD_IA"
251 | } else if transition.StorageClass == "COLD" {
252 | storageClass = "GLACIER"
253 | }
254 | xml = append(xml, fmt.Sprintf("%s%s", temp, storageClass))
255 | } else {
256 | xml = append(xml, fmt.Sprintf("%s%s", temp, transition.StorageClass))
257 | }
258 | }
259 | }
260 | return strings.Join(xml, "")
261 | }
262 | return ""
263 | }
264 |
265 | func convertExpirationToXml(expiration Expiration) string {
266 | if expiration.Days > 0 {
267 | return fmt.Sprintf("%d", expiration.Days)
268 | } else if !expiration.Date.IsZero() {
269 | return fmt.Sprintf("%s", expiration.Date.UTC().Format(ISO8601_MIDNIGHT_DATE_FORMAT))
270 | }
271 | return ""
272 | }
273 | func convertNoncurrentVersionTransitionsToXml(noncurrentVersionTransitions []NoncurrentVersionTransition, isObs bool) string {
274 | if length := len(noncurrentVersionTransitions); length > 0 {
275 | xml := make([]string, 0, length)
276 | for _, noncurrentVersionTransition := range noncurrentVersionTransitions {
277 | if noncurrentVersionTransition.NoncurrentDays > 0 {
278 | storageClass := string(noncurrentVersionTransition.StorageClass)
279 | if !isObs {
280 | if storageClass == "WARM" {
281 | storageClass = "STANDARD_IA"
282 | } else if storageClass == "COLD" {
283 | storageClass = "GLACIER"
284 | }
285 | }
286 | xml = append(xml, fmt.Sprintf("%d"+
287 | "%s",
288 | noncurrentVersionTransition.NoncurrentDays, storageClass))
289 | }
290 | }
291 | return strings.Join(xml, "")
292 | }
293 | return ""
294 | }
295 | func convertNoncurrentVersionExpirationToXml(noncurrentVersionExpiration NoncurrentVersionExpiration) string {
296 | if noncurrentVersionExpiration.NoncurrentDays > 0 {
297 | return fmt.Sprintf("%d", noncurrentVersionExpiration.NoncurrentDays)
298 | }
299 | return ""
300 | }
301 |
302 | func ConvertLifecyleConfigurationToXml(input BucketLifecyleConfiguration, returnMd5 bool, isObs bool) (data string, md5 string) {
303 | xml := make([]string, 0, 2+len(input.LifecycleRules)*9)
304 | xml = append(xml, "")
305 | for _, lifecyleRule := range input.LifecycleRules {
306 | xml = append(xml, "")
307 | if lifecyleRule.ID != "" {
308 | lifecyleRuleID := XmlTranscoding(lifecyleRule.ID)
309 | xml = append(xml, fmt.Sprintf("%s", lifecyleRuleID))
310 | }
311 | lifecyleRulePrefix := XmlTranscoding(lifecyleRule.Prefix)
312 | xml = append(xml, fmt.Sprintf("%s", lifecyleRulePrefix))
313 | xml = append(xml, fmt.Sprintf("%s", lifecyleRule.Status))
314 | if ret := convertTransitionsToXml(lifecyleRule.Transitions, isObs); ret != "" {
315 | xml = append(xml, ret)
316 | }
317 | if ret := convertExpirationToXml(lifecyleRule.Expiration); ret != "" {
318 | xml = append(xml, ret)
319 | }
320 | if ret := convertNoncurrentVersionTransitionsToXml(lifecyleRule.NoncurrentVersionTransitions, isObs); ret != "" {
321 | xml = append(xml, ret)
322 | }
323 | if ret := convertNoncurrentVersionExpirationToXml(lifecyleRule.NoncurrentVersionExpiration); ret != "" {
324 | xml = append(xml, ret)
325 | }
326 | xml = append(xml, "")
327 | }
328 | xml = append(xml, "")
329 | data = strings.Join(xml, "")
330 | if returnMd5 {
331 | md5 = Base64Md5([]byte(data))
332 | }
333 | return
334 | }
335 |
336 | func converntFilterRulesToXml(filterRules []FilterRule, isObs bool) string {
337 | if length := len(filterRules); length > 0 {
338 | xml := make([]string, 0, length*4)
339 | for _, filterRule := range filterRules {
340 | xml = append(xml, "")
341 | if filterRule.Name != "" {
342 | filterRuleName := XmlTranscoding(filterRule.Name)
343 | xml = append(xml, fmt.Sprintf("%s", filterRuleName))
344 | }
345 | if filterRule.Value != "" {
346 | filterRuleValue := XmlTranscoding(filterRule.Value)
347 | xml = append(xml, fmt.Sprintf("%s", filterRuleValue))
348 | }
349 | xml = append(xml, "")
350 | }
351 | if !isObs {
352 | return fmt.Sprintf("%s", strings.Join(xml, ""))
353 | } else {
354 | return fmt.Sprintf("", strings.Join(xml, ""))
355 | }
356 | }
357 | return ""
358 | }
359 |
360 | func converntEventsToXml(events []EventType, isObs bool) string {
361 | if length := len(events); length > 0 {
362 | xml := make([]string, 0, length)
363 | if !isObs {
364 | for _, event := range events {
365 | xml = append(xml, fmt.Sprintf("%s%s", "s3:", event))
366 | }
367 | } else {
368 | for _, event := range events {
369 | xml = append(xml, fmt.Sprintf("%s", event))
370 | }
371 | }
372 | return strings.Join(xml, "")
373 | }
374 | return ""
375 | }
376 |
377 | func converntConfigureToXml(topicConfiguration TopicConfiguration, xmlElem string, isObs bool) string {
378 | xml := make([]string, 0, 6)
379 | xml = append(xml, xmlElem)
380 | if topicConfiguration.ID != "" {
381 | topicConfigurationID := XmlTranscoding(topicConfiguration.ID)
382 | xml = append(xml, fmt.Sprintf("%s", topicConfigurationID))
383 | }
384 | topicConfigurationTopic := XmlTranscoding(topicConfiguration.Topic)
385 | xml = append(xml, fmt.Sprintf("%s", topicConfigurationTopic))
386 |
387 | if ret := converntEventsToXml(topicConfiguration.Events, isObs); ret != "" {
388 | xml = append(xml, ret)
389 | }
390 | if ret := converntFilterRulesToXml(topicConfiguration.FilterRules, isObs); ret != "" {
391 | xml = append(xml, ret)
392 | }
393 | xml = append(xml, xmlElem)
394 | return strings.Join(xml, "")
395 | }
396 |
397 | func ConverntObsRestoreToXml(restoreObjectInput RestoreObjectInput) string {
398 | xml := make([]string, 0, 2)
399 | xml = append(xml, fmt.Sprintf("%d", restoreObjectInput.Days))
400 | if restoreObjectInput.Tier != "Bulk" {
401 | xml = append(xml, fmt.Sprintf("%s", restoreObjectInput.Tier))
402 | }
403 | xml = append(xml, fmt.Sprintf(""))
404 | data := strings.Join(xml, "")
405 | return data
406 | }
407 |
408 | func ConvertNotificationToXml(input BucketNotification, returnMd5 bool, isObs bool) (data string, md5 string) {
409 | xml := make([]string, 0, 2+len(input.TopicConfigurations)*6)
410 | xml = append(xml, "")
411 | for _, topicConfiguration := range input.TopicConfigurations {
412 | ret := converntConfigureToXml(topicConfiguration, "", isObs)
413 | xml = append(xml, ret)
414 | }
415 | xml = append(xml, "")
416 | data = strings.Join(xml, "")
417 | if returnMd5 {
418 | md5 = Base64Md5([]byte(data))
419 | }
420 | return
421 | }
422 |
423 | func ConvertCompleteMultipartUploadInputToXml(input CompleteMultipartUploadInput, returnMd5 bool) (data string, md5 string) {
424 | xml := make([]string, 0, 2+len(input.Parts)*4)
425 | xml = append(xml, "")
426 | for _, part := range input.Parts {
427 | xml = append(xml, "")
428 | xml = append(xml, fmt.Sprintf("%d", part.PartNumber))
429 | xml = append(xml, fmt.Sprintf("%s", part.ETag))
430 | xml = append(xml, "")
431 | }
432 | xml = append(xml, "")
433 | data = strings.Join(xml, "")
434 | if returnMd5 {
435 | md5 = Base64Md5([]byte(data))
436 | }
437 | return
438 | }
439 |
440 | func parseSseHeader(responseHeaders map[string][]string) (sseHeader ISseHeader) {
441 | if ret, ok := responseHeaders[HEADER_SSEC_ENCRYPTION]; ok {
442 | sseCHeader := SseCHeader{Encryption: ret[0]}
443 | if ret, ok = responseHeaders[HEADER_SSEC_KEY_MD5]; ok {
444 | sseCHeader.KeyMD5 = ret[0]
445 | }
446 | sseHeader = sseCHeader
447 | } else if ret, ok := responseHeaders[HEADER_SSEKMS_ENCRYPTION]; ok {
448 | sseKmsHeader := SseKmsHeader{Encryption: ret[0]}
449 | if ret, ok = responseHeaders[HEADER_SSEKMS_KEY]; ok {
450 | sseKmsHeader.Key = ret[0]
451 | } else if ret, ok = responseHeaders[HEADER_SSEKMS_ENCRYPT_KEY_OBS]; ok {
452 | sseKmsHeader.Key = ret[0]
453 | }
454 | sseHeader = sseKmsHeader
455 | }
456 | return
457 | }
458 |
459 | func ParseGetObjectMetadataOutput(output *GetObjectMetadataOutput) {
460 | if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
461 | output.VersionId = ret[0]
462 | }
463 | if ret, ok := output.ResponseHeaders[HEADER_WEBSITE_REDIRECT_LOCATION]; ok {
464 | output.WebsiteRedirectLocation = ret[0]
465 | }
466 | if ret, ok := output.ResponseHeaders[HEADER_EXPIRATION]; ok {
467 | output.Expiration = ret[0]
468 | }
469 | if ret, ok := output.ResponseHeaders[HEADER_RESTORE]; ok {
470 | output.Restore = ret[0]
471 | }
472 | if ret, ok := output.ResponseHeaders[HEADER_OBJECT_TYPE]; ok {
473 | output.Restore = ret[0]
474 | }
475 | if ret, ok := output.ResponseHeaders[HEADER_NEXT_APPEND_POSITION]; ok {
476 | output.Restore = ret[0]
477 | }
478 | if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok {
479 | output.StorageClass = ParseStringToStorageClassType(ret[0])
480 | }
481 | if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok {
482 | output.ETag = ret[0]
483 | }
484 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_TYPE]; ok {
485 | output.ContentType = ret[0]
486 | }
487 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_ORIGIN]; ok {
488 | output.AllowOrigin = ret[0]
489 | }
490 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_HEADERS]; ok {
491 | output.AllowHeader = ret[0]
492 | }
493 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_MAX_AGE]; ok {
494 | output.MaxAgeSeconds = StringToInt(ret[0], 0)
495 | }
496 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_METHODS]; ok {
497 | output.AllowMethod = ret[0]
498 | }
499 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_EXPOSE_HEADERS]; ok {
500 | output.ExposeHeader = ret[0]
501 | }
502 |
503 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
504 | if ret, ok := output.ResponseHeaders[HEADER_LASTMODIFIED]; ok {
505 | ret, err := time.Parse(time.RFC1123, ret[0])
506 | if err == nil {
507 | output.LastModified = ret
508 | }
509 | }
510 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LENGTH]; ok {
511 | output.ContentLength = StringToInt64(ret[0], 0)
512 | }
513 |
514 | output.Metadata = make(map[string]string)
515 |
516 | for key, value := range output.ResponseHeaders {
517 | if strings.HasPrefix(key, PREFIX_META) {
518 | _key := key[len(PREFIX_META):]
519 | output.ResponseHeaders[_key] = value
520 | output.Metadata[_key] = value[0]
521 | delete(output.ResponseHeaders, key)
522 | }
523 | }
524 |
525 | }
526 |
527 | func ParseCopyObjectOutput(output *CopyObjectOutput) {
528 | if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
529 | output.VersionId = ret[0]
530 | }
531 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
532 | if ret, ok := output.ResponseHeaders[HEADER_COPY_SOURCE_VERSION_ID]; ok {
533 | output.CopySourceVersionId = ret[0]
534 | }
535 | }
536 |
537 | func ParsePutObjectOutput(output *PutObjectOutput) {
538 | if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
539 | output.VersionId = ret[0]
540 | }
541 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
542 | if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok {
543 | output.StorageClass = ParseStringToStorageClassType(ret[0])
544 | }
545 | if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok {
546 | output.ETag = ret[0]
547 | }
548 | }
549 |
550 | func ParseInitiateMultipartUploadOutput(output *InitiateMultipartUploadOutput) {
551 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
552 | }
553 |
554 | func ParseUploadPartOutput(output *UploadPartOutput) {
555 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
556 | if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok {
557 | output.ETag = ret[0]
558 | }
559 | }
560 |
561 | func ParseCompleteMultipartUploadOutput(output *CompleteMultipartUploadOutput) {
562 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
563 | if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
564 | output.VersionId = ret[0]
565 | }
566 | }
567 |
568 | func ParseCopyPartOutput(output *CopyPartOutput) {
569 | output.SseHeader = parseSseHeader(output.ResponseHeaders)
570 | }
571 |
572 | func ParseGetBucketMetadataOutput(output *GetBucketMetadataOutput) {
573 | if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS]; ok {
574 | output.StorageClass = ParseStringToStorageClassType(ret[0])
575 | } else if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok {
576 | output.StorageClass = ParseStringToStorageClassType(ret[0])
577 | }
578 | if ret, ok := output.ResponseHeaders[HEADER_VERSION_OBS]; ok {
579 | output.Version = ret[0]
580 | }
581 | if ret, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
582 | output.Location = ret[0]
583 | } else if ret, ok := output.ResponseHeaders[HEADER_BUCKET_LOCATION_OBS]; ok {
584 | output.Location = ret[0]
585 | }
586 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_ORIGIN]; ok {
587 | output.AllowOrigin = ret[0]
588 | }
589 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_HEADERS]; ok {
590 | output.AllowHeader = ret[0]
591 | }
592 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_MAX_AGE]; ok {
593 | output.MaxAgeSeconds = StringToInt(ret[0], 0)
594 | }
595 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_METHODS]; ok {
596 | output.AllowMethod = ret[0]
597 | }
598 | if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_EXPOSE_HEADERS]; ok {
599 | output.ExposeHeader = ret[0]
600 | }
601 | if ret, ok := output.ResponseHeaders[HEADER_EPID_HEADERS]; ok {
602 | output.Epid = ret[0]
603 | }
604 | }
605 |
606 | func ParseSetObjectMetadataOutput(output *SetObjectMetadataOutput) {
607 | if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS]; ok {
608 | output.StorageClass = ParseStringToStorageClassType(ret[0])
609 | } else if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok {
610 | output.StorageClass = ParseStringToStorageClassType(ret[0])
611 | }
612 | if ret, ok := output.ResponseHeaders[HEADER_METADATA_DIRECTIVE]; ok {
613 | output.MetadataDirective = MetadataDirectiveType(ret[0])
614 | }
615 | if ret, ok := output.ResponseHeaders[HEADER_CACHE_CONTROL]; ok {
616 | output.CacheControl = ret[0]
617 | }
618 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_DISPOSITION]; ok {
619 | output.ContentDisposition = ret[0]
620 | }
621 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_ENCODING]; ok {
622 | output.ContentEncoding = ret[0]
623 | }
624 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LANGUAGE]; ok {
625 | output.ContentLanguage = ret[0]
626 | }
627 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_TYPE]; ok {
628 | output.ContentType = ret[0]
629 | }
630 | if ret, ok := output.ResponseHeaders[HEADER_EXPIRES]; ok {
631 | output.Expires = ret[0]
632 | }
633 | if ret, ok := output.ResponseHeaders[HEADER_WEBSITE_REDIRECT_LOCATION]; ok {
634 | output.WebsiteRedirectLocation = ret[0]
635 | }
636 | output.Metadata = make(map[string]string)
637 |
638 | for key, value := range output.ResponseHeaders {
639 | if strings.HasPrefix(key, PREFIX_META) {
640 | _key := key[len(PREFIX_META):]
641 | output.ResponseHeaders[_key] = value
642 | output.Metadata[_key] = value[0]
643 | delete(output.ResponseHeaders, key)
644 | }
645 | }
646 | }
647 | func ParseDeleteObjectOutput(output *DeleteObjectOutput) {
648 | if versionId, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
649 | output.VersionId = versionId[0]
650 | }
651 |
652 | if deleteMarker, ok := output.ResponseHeaders[HEADER_DELETE_MARKER]; ok {
653 | output.DeleteMarker = deleteMarker[0] == "true"
654 | }
655 | }
656 |
657 | func ParseGetObjectOutput(output *GetObjectOutput) {
658 | ParseGetObjectMetadataOutput(&output.GetObjectMetadataOutput)
659 | if ret, ok := output.ResponseHeaders[HEADER_DELETE_MARKER]; ok {
660 | output.DeleteMarker = ret[0] == "true"
661 | }
662 | if ret, ok := output.ResponseHeaders[HEADER_CACHE_CONTROL]; ok {
663 | output.CacheControl = ret[0]
664 | }
665 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_DISPOSITION]; ok {
666 | output.ContentDisposition = ret[0]
667 | }
668 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_ENCODING]; ok {
669 | output.ContentEncoding = ret[0]
670 | }
671 | if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LANGUAGE]; ok {
672 | output.ContentLanguage = ret[0]
673 | }
674 | if ret, ok := output.ResponseHeaders[HEADER_EXPIRES]; ok {
675 | output.Expires = ret[0]
676 | }
677 | }
678 |
679 | func ConvertRequestToIoReaderV2(req interface{}) (io.Reader, string, error) {
680 | data, err := TransToXml(req)
681 | if err == nil {
682 | if isDebugLogEnabled() {
683 | doLog(LEVEL_DEBUG, "Do http request with data: %s", string(data))
684 | }
685 | return bytes.NewReader(data), Base64Md5(data), nil
686 | }
687 | return nil, "", err
688 | }
689 |
690 | func ConvertRequestToIoReader(req interface{}) (io.Reader, error) {
691 | body, err := TransToXml(req)
692 | if err == nil {
693 | if isDebugLogEnabled() {
694 | doLog(LEVEL_DEBUG, "Do http request with data: %s", string(body))
695 | }
696 | return bytes.NewReader(body), nil
697 | }
698 | return nil, err
699 | }
700 |
701 | func ParseResponseToBaseModel(resp *http.Response, baseModel IBaseModel, xmlResult bool, isObs bool) (err error) {
702 | readCloser, ok := baseModel.(IReadCloser)
703 | if !ok {
704 | defer resp.Body.Close()
705 | body, err := ioutil.ReadAll(resp.Body)
706 | if err == nil && len(body) > 0 {
707 | if xmlResult {
708 | err = ParseXml(body, baseModel)
709 | if err != nil {
710 | doLog(LEVEL_ERROR, "Unmarshal error: %v", err)
711 | }
712 | } else {
713 | s := reflect.TypeOf(baseModel).Elem()
714 | for i := 0; i < s.NumField(); i++ {
715 | if s.Field(i).Tag == "body" {
716 | reflect.ValueOf(baseModel).Elem().FieldByName(s.Field(i).Name).SetString(string(body))
717 | break
718 | }
719 | }
720 | }
721 | }
722 | } else {
723 | readCloser.setReadCloser(resp.Body)
724 | }
725 |
726 | baseModel.setStatusCode(resp.StatusCode)
727 | responseHeaders := cleanHeaderPrefix(resp.Header, isObs)
728 | baseModel.setResponseHeaders(responseHeaders)
729 | if values, ok := responseHeaders[HEADER_REQUEST_ID]; ok {
730 | baseModel.setRequestId(values[0])
731 | }
732 | return
733 | }
734 |
735 | func ParseResponseToObsError(resp *http.Response, isObs bool) error {
736 | obsError := ObsError{}
737 | ParseResponseToBaseModel(resp, &obsError, true, isObs)
738 | obsError.Status = resp.Status
739 | return obsError
740 | }
741 |
--------------------------------------------------------------------------------