terraform/vendor/github.com/tencentyun/cos-go-sdk-v5/cos.go

347 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cos
import (
"bytes"
"context"
"encoding/base64"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"text/template"
"strconv"
"github.com/google/go-querystring/query"
"github.com/mozillazg/go-httpheader"
)
const (
// Version current go sdk version
Version = "0.7.3"
userAgent = "cos-go-sdk-v5/" + Version
contentTypeXML = "application/xml"
defaultServiceBaseURL = "http://service.cos.myqcloud.com"
)
var bucketURLTemplate = template.Must(
template.New("bucketURLFormat").Parse(
"{{.Schema}}://{{.BucketName}}.cos.{{.Region}}.myqcloud.com",
),
)
// BaseURL 访问各 API 所需的基础 URL
type BaseURL struct {
// 访问 bucket, object 相关 API 的基础 URL不包含 path 部分): http://example.com
BucketURL *url.URL
// 访问 service API 的基础 URL不包含 path 部分): http://example.com
ServiceURL *url.URL
}
// NewBucketURL 生成 BaseURL 所需的 BucketURL
//
// bucketName: bucket名称, bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
// Region: 区域代码: ap-beijing-1,ap-beijing,ap-shanghai,ap-guangzhou...
// secure: 是否使用 https
func NewBucketURL(bucketName, region string, secure bool) *url.URL {
schema := "https"
if !secure {
schema = "http"
}
w := bytes.NewBuffer(nil)
bucketURLTemplate.Execute(w, struct {
Schema string
BucketName string
Region string
}{
schema, bucketName, region,
})
u, _ := url.Parse(w.String())
return u
}
// Client is a client manages communication with the COS API.
type Client struct {
client *http.Client
UserAgent string
BaseURL *BaseURL
common service
Service *ServiceService
Bucket *BucketService
Object *ObjectService
}
type service struct {
client *Client
}
// NewClient returns a new COS API client.
func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = &http.Client{}
}
baseURL := &BaseURL{}
if uri != nil {
baseURL.BucketURL = uri.BucketURL
baseURL.ServiceURL = uri.ServiceURL
}
if baseURL.ServiceURL == nil {
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
}
c := &Client{
client: httpClient,
UserAgent: userAgent,
BaseURL: baseURL,
}
c.common.client = c
c.Service = (*ServiceService)(&c.common)
c.Bucket = (*BucketService)(&c.common)
c.Object = (*ObjectService)(&c.common)
return c
}
func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) {
uri, err = addURLOptions(uri, optQuery)
if err != nil {
return
}
u, _ := url.Parse(uri)
urlStr := baseURL.ResolveReference(u).String()
var reader io.Reader
contentType := ""
contentMD5 := ""
if body != nil {
// 上传文件
if r, ok := body.(io.Reader); ok {
reader = r
} else {
b, err := xml.Marshal(body)
if err != nil {
return nil, err
}
contentType = contentTypeXML
reader = bytes.NewReader(b)
contentMD5 = base64.StdEncoding.EncodeToString(calMD5Digest(b))
}
}
req, err = http.NewRequest(method, urlStr, reader)
if err != nil {
return
}
req.Header, err = addHeaderOptions(req.Header, optHeader)
if err != nil {
return
}
if v := req.Header.Get("Content-Length"); req.ContentLength == 0 && v != "" && v != "0" {
req.ContentLength, _ = strconv.ParseInt(v, 10, 64)
}
if contentMD5 != "" {
req.Header["Content-MD5"] = []string{contentMD5}
}
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
if req.Header.Get("Content-Type") == "" && contentType != "" {
req.Header.Set("Content-Type", contentType)
}
return
}
func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{}, closeBody bool) (*Response, error) {
req = req.WithContext(ctx)
resp, err := c.client.Do(req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return nil, err
}
defer func() {
if closeBody {
// Close the body to let the Transport reuse the connection
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}
}()
response := newResponse(resp)
err = checkResponse(resp)
if err != nil {
// even though there was an error, we still return the response
// in case the caller wants to inspect it further
return response, err
}
if result != nil {
if w, ok := result.(io.Writer); ok {
io.Copy(w, resp.Body)
} else {
err = xml.NewDecoder(resp.Body).Decode(result)
if err == io.EOF {
err = nil // ignore EOF errors caused by empty response body
}
}
}
return response, err
}
type sendOptions struct {
// 基础 URL
baseURL *url.URL
// URL 中除基础 URL 外的剩余部分
uri string
// 请求方法
method string
body interface{}
// url 查询参数
optQuery interface{}
// http header 参数
optHeader interface{}
// 用 result 反序列化 resp.Body
result interface{}
// 是否禁用自动调用 resp.Body.Close()
// 自动调用 Close() 是为了能够重用连接
disableCloseBody bool
}
func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, err error) {
req, err := c.newRequest(ctx, opt.baseURL, opt.uri, opt.method, opt.body, opt.optQuery, opt.optHeader)
if err != nil {
return
}
resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
if err != nil {
return
}
return
}
// addURLOptions adds the parameters in opt as URL query parameters to s. opt
// must be a struct whose fields may contain "url" tags.
func addURLOptions(s string, opt interface{}) (string, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opt)
if err != nil {
return s, err
}
// 保留原有的参数,并且放在前面。因为 cos 的 url 路由是以第一个参数作为路由的
// e.g. /?uploads
q := u.RawQuery
rq := qs.Encode()
if q != "" {
if rq != "" {
u.RawQuery = fmt.Sprintf("%s&%s", q, qs.Encode())
}
} else {
u.RawQuery = rq
}
return u.String(), nil
}
// addHeaderOptions adds the parameters in opt as Header fields to req. opt
// must be a struct whose fields may contain "header" tags.
func addHeaderOptions(header http.Header, opt interface{}) (http.Header, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return header, nil
}
h, err := httpheader.Header(opt)
if err != nil {
return nil, err
}
for key, values := range h {
for _, value := range values {
header.Add(key, value)
}
}
return header, nil
}
// Owner defines Bucket/Object's owner
type Owner struct {
UIN string `xml:"uin,omitempty"`
ID string `xml:",omitempty"`
DisplayName string `xml:",omitempty"`
}
// Initiator same to the Owner struct
type Initiator Owner
// Response API 响应
type Response struct {
*http.Response
}
func newResponse(resp *http.Response) *Response {
return &Response{
Response: resp,
}
}
// ACLHeaderOptions is the option of ACLHeader
type ACLHeaderOptions struct {
XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"`
XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"`
XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"`
XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"`
}
// ACLGrantee is the param of ACLGrant
type ACLGrantee struct {
Type string `xml:"type,attr"`
UIN string `xml:"uin,omitempty"`
URI string `xml:"URI,omitempty"`
ID string `xml:",omitempty"`
DisplayName string `xml:",omitempty"`
SubAccount string `xml:"Subaccount,omitempty"`
}
// ACLGrant is the param of ACLXml
type ACLGrant struct {
Grantee *ACLGrantee
Permission string
}
// ACLXml is the ACL body struct
type ACLXml struct {
XMLName xml.Name `xml:"AccessControlPolicy"`
Owner *Owner
AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
}