You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

352 lines
8.0 KiB

6 years ago
  1. package cos
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "encoding/xml"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "reflect"
  13. "text/template"
  14. "strconv"
  15. "github.com/google/go-querystring/query"
  16. "github.com/mozillazg/go-httpheader"
  17. )
  18. const (
  19. // Version ...
  20. Version = "0.7.3"
  21. userAgent = "cos-go-sdk-v5/" + Version
  22. contentTypeXML = "application/xml"
  23. defaultServiceBaseURL = "https://service.cos.myqcloud.com"
  24. )
  25. var bucketURLTemplate = template.Must(
  26. template.New("bucketURLFormat").Parse(
  27. "{{.Schema}}://{{.BucketName}}.cos.{{.Region}}.myqcloud.com",
  28. ),
  29. )
  30. // BaseURL 访问各 API 所需的基础 URL
  31. type BaseURL struct {
  32. // 访问 bucket, object 相关 API 的基础 URL(不包含 path 部分): http://example.com
  33. BucketURL *url.URL
  34. // 访问 service API 的基础 URL(不包含 path 部分): http://example.com
  35. ServiceURL *url.URL
  36. }
  37. // NewBucketURL 生成 BaseURL 所需的 BucketURL
  38. //
  39. // bucketName: bucket名称, bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
  40. // Region: 区域代码: ap-beijing-1,ap-beijing,ap-shanghai,ap-guangzhou...
  41. // secure: 是否使用 https
  42. func NewBucketURL(bucketName, region string, secure bool) *url.URL {
  43. schema := "https"
  44. if !secure {
  45. schema = "http"
  46. }
  47. w := bytes.NewBuffer(nil)
  48. bucketURLTemplate.Execute(w, struct {
  49. Schema string
  50. BucketName string
  51. Region string
  52. }{
  53. schema, bucketName, region,
  54. })
  55. u, _ := url.Parse(w.String())
  56. return u
  57. }
  58. // A Client manages communication with the COS API.
  59. type Client struct {
  60. client *http.Client
  61. UserAgent string
  62. BaseURL *BaseURL
  63. common service
  64. Service *ServiceService
  65. Bucket *BucketService
  66. Object *ObjectService
  67. }
  68. type service struct {
  69. client *Client
  70. }
  71. // NewClient returns a new COS API client.
  72. func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
  73. if httpClient == nil {
  74. httpClient = &http.Client{}
  75. }
  76. baseURL := &BaseURL{}
  77. if uri != nil {
  78. baseURL.BucketURL = uri.BucketURL
  79. baseURL.ServiceURL = uri.ServiceURL
  80. }
  81. if baseURL.ServiceURL == nil {
  82. baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
  83. }
  84. c := &Client{
  85. client: httpClient,
  86. UserAgent: userAgent,
  87. BaseURL: baseURL,
  88. }
  89. c.common.client = c
  90. c.Service = (*ServiceService)(&c.common)
  91. c.Bucket = (*BucketService)(&c.common)
  92. c.Object = (*ObjectService)(&c.common)
  93. return c
  94. }
  95. func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) {
  96. uri, err = addURLOptions(uri, optQuery)
  97. if err != nil {
  98. return
  99. }
  100. u, _ := url.Parse(uri)
  101. urlStr := baseURL.ResolveReference(u).String()
  102. var reader io.Reader
  103. contentType := ""
  104. contentMD5 := ""
  105. xsha1 := ""
  106. if body != nil {
  107. // 上传文件
  108. if r, ok := body.(io.Reader); ok {
  109. reader = r
  110. } else {
  111. b, err := xml.Marshal(body)
  112. if err != nil {
  113. return nil, err
  114. }
  115. contentType = contentTypeXML
  116. reader = bytes.NewReader(b)
  117. contentMD5 = base64.StdEncoding.EncodeToString(calMD5Digest(b))
  118. //xsha1 = base64.StdEncoding.EncodeToString(calSHA1Digest(b))
  119. }
  120. } else {
  121. contentType = contentTypeXML
  122. }
  123. req, err = http.NewRequest(method, urlStr, reader)
  124. if err != nil {
  125. return
  126. }
  127. req.Header, err = addHeaderOptions(req.Header, optHeader)
  128. if err != nil {
  129. return
  130. }
  131. if v := req.Header.Get("Content-Length"); req.ContentLength == 0 && v != "" && v != "0" {
  132. req.ContentLength, _ = strconv.ParseInt(v, 10, 64)
  133. }
  134. if contentMD5 != "" {
  135. req.Header["Content-MD5"] = []string{contentMD5}
  136. }
  137. if xsha1 != "" {
  138. req.Header.Set("x-cos-sha1", xsha1)
  139. }
  140. if c.UserAgent != "" {
  141. req.Header.Set("User-Agent", c.UserAgent)
  142. }
  143. if req.Header.Get("Content-Type") == "" && contentType != "" {
  144. req.Header.Set("Content-Type", contentType)
  145. }
  146. return
  147. }
  148. func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{}, closeBody bool) (*Response, error) {
  149. req = req.WithContext(ctx)
  150. resp, err := c.client.Do(req)
  151. if err != nil {
  152. // If we got an error, and the context has been canceled,
  153. // the context's error is probably more useful.
  154. select {
  155. case <-ctx.Done():
  156. return nil, ctx.Err()
  157. default:
  158. }
  159. return nil, err
  160. }
  161. defer func() {
  162. if closeBody {
  163. // Close the body to let the Transport reuse the connection
  164. io.Copy(ioutil.Discard, resp.Body)
  165. resp.Body.Close()
  166. }
  167. }()
  168. response := newResponse(resp)
  169. err = checkResponse(resp)
  170. if err != nil {
  171. // even though there was an error, we still return the response
  172. // in case the caller wants to inspect it further
  173. return response, err
  174. }
  175. if result != nil {
  176. if w, ok := result.(io.Writer); ok {
  177. io.Copy(w, resp.Body)
  178. } else {
  179. err = xml.NewDecoder(resp.Body).Decode(result)
  180. if err == io.EOF {
  181. err = nil // ignore EOF errors caused by empty response body
  182. }
  183. }
  184. }
  185. return response, err
  186. }
  187. type sendOptions struct {
  188. // 基础 URL
  189. baseURL *url.URL
  190. // URL 中除基础 URL 外的剩余部分
  191. uri string
  192. // 请求方法
  193. method string
  194. body interface{}
  195. // url 查询参数
  196. optQuery interface{}
  197. // http header 参数
  198. optHeader interface{}
  199. // 用 result 反序列化 resp.Body
  200. result interface{}
  201. // 是否禁用自动调用 resp.Body.Close()
  202. // 自动调用 Close() 是为了能够重用连接
  203. disableCloseBody bool
  204. }
  205. func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, err error) {
  206. req, err := c.newRequest(ctx, opt.baseURL, opt.uri, opt.method, opt.body, opt.optQuery, opt.optHeader)
  207. if err != nil {
  208. return
  209. }
  210. resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
  211. if err != nil {
  212. return
  213. }
  214. return
  215. }
  216. // addURLOptions adds the parameters in opt as URL query parameters to s. opt
  217. // must be a struct whose fields may contain "url" tags.
  218. func addURLOptions(s string, opt interface{}) (string, error) {
  219. v := reflect.ValueOf(opt)
  220. if v.Kind() == reflect.Ptr && v.IsNil() {
  221. return s, nil
  222. }
  223. u, err := url.Parse(s)
  224. if err != nil {
  225. return s, err
  226. }
  227. qs, err := query.Values(opt)
  228. if err != nil {
  229. return s, err
  230. }
  231. // 保留原有的参数,并且放在前面。因为 cos 的 url 路由是以第一个参数作为路由的
  232. // e.g. /?uploads
  233. q := u.RawQuery
  234. rq := qs.Encode()
  235. if q != "" {
  236. if rq != "" {
  237. u.RawQuery = fmt.Sprintf("%s&%s", q, qs.Encode())
  238. }
  239. } else {
  240. u.RawQuery = rq
  241. }
  242. return u.String(), nil
  243. }
  244. // addHeaderOptions adds the parameters in opt as Header fields to req. opt
  245. // must be a struct whose fields may contain "header" tags.
  246. func addHeaderOptions(header http.Header, opt interface{}) (http.Header, error) {
  247. v := reflect.ValueOf(opt)
  248. if v.Kind() == reflect.Ptr && v.IsNil() {
  249. return header, nil
  250. }
  251. h, err := httpheader.Header(opt)
  252. if err != nil {
  253. return nil, err
  254. }
  255. for key, values := range h {
  256. for _, value := range values {
  257. header.Add(key, value)
  258. }
  259. }
  260. return header, nil
  261. }
  262. // Owner ...
  263. type Owner struct {
  264. UIN string `xml:"uin,omitempty"`
  265. ID string `xml:",omitempty"`
  266. DisplayName string `xml:",omitempty"`
  267. }
  268. // Initiator ...
  269. type Initiator Owner
  270. // Response API 响应
  271. type Response struct {
  272. *http.Response
  273. }
  274. func newResponse(resp *http.Response) *Response {
  275. return &Response{
  276. Response: resp,
  277. }
  278. }
  279. // ACLHeaderOptions ...
  280. type ACLHeaderOptions struct {
  281. XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"`
  282. XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"`
  283. XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"`
  284. XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"`
  285. }
  286. // ACLGrantee ...
  287. type ACLGrantee struct {
  288. Type string `xml:"type,attr"`
  289. UIN string `xml:"uin,omitempty"`
  290. ID string `xml:",omitempty"`
  291. DisplayName string `xml:",omitempty"`
  292. SubAccount string `xml:"Subaccount,omitempty"`
  293. }
  294. // ACLGrant ...
  295. type ACLGrant struct {
  296. Grantee *ACLGrantee
  297. Permission string
  298. }
  299. // ACLXml ...
  300. type ACLXml struct {
  301. XMLName xml.Name `xml:"AccessControlPolicy"`
  302. Owner *Owner
  303. AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
  304. }