├── 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("%s", 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 | --------------------------------------------------------------------------------