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.

355 lines
8.3 KiB

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