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.

489 lines
12 KiB

4 years ago
6 years ago
3 years ago
5 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
4 years ago
4 years ago
5 years ago
4 years ago
4 years ago
4 years ago
5 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
4 years ago
4 years ago
4 years ago
4 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. "strings"
  14. "text/template"
  15. "strconv"
  16. "github.com/google/go-querystring/query"
  17. "github.com/mozillazg/go-httpheader"
  18. )
  19. const (
  20. // Version current go sdk version
  21. Version = "0.7.31"
  22. userAgent = "cos-go-sdk-v5/" + Version
  23. contentTypeXML = "application/xml"
  24. defaultServiceBaseURL = "http://service.cos.myqcloud.com"
  25. )
  26. var bucketURLTemplate = template.Must(
  27. template.New("bucketURLFormat").Parse(
  28. "{{.Schema}}://{{.BucketName}}.cos.{{.Region}}.myqcloud.com",
  29. ),
  30. )
  31. // BaseURL 访问各 API 所需的基础 URL
  32. type BaseURL struct {
  33. // 访问 bucket, object 相关 API 的基础 URL(不包含 path 部分): http://example.com
  34. BucketURL *url.URL
  35. // 访问 service API 的基础 URL(不包含 path 部分): http://example.com
  36. ServiceURL *url.URL
  37. // 访问 job API 的基础 URL (不包含 path 部分): http://example.com
  38. BatchURL *url.URL
  39. // 访问 CI 的基础 URL
  40. CIURL *url.URL
  41. }
  42. // NewBucketURL 生成 BaseURL 所需的 BucketURL
  43. //
  44. // bucketName: bucket名称, bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
  45. // Region: 区域代码: ap-beijing-1,ap-beijing,ap-shanghai,ap-guangzhou...
  46. // secure: 是否使用 https
  47. func NewBucketURL(bucketName, region string, secure bool) *url.URL {
  48. schema := "https"
  49. if !secure {
  50. schema = "http"
  51. }
  52. w := bytes.NewBuffer(nil)
  53. bucketURLTemplate.Execute(w, struct {
  54. Schema string
  55. BucketName string
  56. Region string
  57. }{
  58. schema, bucketName, region,
  59. })
  60. u, _ := url.Parse(w.String())
  61. return u
  62. }
  63. type Config struct {
  64. EnableCRC bool
  65. RequestBodyClose bool
  66. }
  67. // Client is a client manages communication with the COS API.
  68. type Client struct {
  69. client *http.Client
  70. Host string
  71. UserAgent string
  72. BaseURL *BaseURL
  73. common service
  74. Service *ServiceService
  75. Bucket *BucketService
  76. Object *ObjectService
  77. Batch *BatchService
  78. CI *CIService
  79. Conf *Config
  80. }
  81. type service struct {
  82. client *Client
  83. }
  84. // NewClient returns a new COS API client.
  85. func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
  86. if httpClient == nil {
  87. httpClient = &http.Client{}
  88. }
  89. baseURL := &BaseURL{}
  90. if uri != nil {
  91. baseURL.BucketURL = uri.BucketURL
  92. baseURL.ServiceURL = uri.ServiceURL
  93. baseURL.BatchURL = uri.BatchURL
  94. baseURL.CIURL = uri.CIURL
  95. }
  96. if baseURL.ServiceURL == nil {
  97. baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
  98. }
  99. c := &Client{
  100. client: httpClient,
  101. UserAgent: userAgent,
  102. BaseURL: baseURL,
  103. Conf: &Config{
  104. EnableCRC: true,
  105. RequestBodyClose: false,
  106. },
  107. }
  108. c.common.client = c
  109. c.Service = (*ServiceService)(&c.common)
  110. c.Bucket = (*BucketService)(&c.common)
  111. c.Object = (*ObjectService)(&c.common)
  112. c.Batch = (*BatchService)(&c.common)
  113. c.CI = (*CIService)(&c.common)
  114. return c
  115. }
  116. type Credential struct {
  117. SecretID string
  118. SecretKey string
  119. SessionToken string
  120. }
  121. func (c *Client) GetCredential() *Credential {
  122. auth, ok := c.client.Transport.(*AuthorizationTransport)
  123. if !ok {
  124. return nil
  125. }
  126. auth.rwLocker.Lock()
  127. defer auth.rwLocker.Unlock()
  128. return &Credential{
  129. SecretID: auth.SecretID,
  130. SecretKey: auth.SecretKey,
  131. SessionToken: auth.SessionToken,
  132. }
  133. }
  134. func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) {
  135. uri, err = addURLOptions(uri, optQuery)
  136. if err != nil {
  137. return
  138. }
  139. u, _ := url.Parse(uri)
  140. urlStr := baseURL.ResolveReference(u).String()
  141. var reader io.Reader
  142. contentType := ""
  143. contentMD5 := ""
  144. if body != nil {
  145. // 上传文件
  146. if r, ok := body.(io.Reader); ok {
  147. reader = r
  148. } else {
  149. b, err := xml.Marshal(body)
  150. if err != nil {
  151. return nil, err
  152. }
  153. contentType = contentTypeXML
  154. reader = bytes.NewReader(b)
  155. contentMD5 = base64.StdEncoding.EncodeToString(calMD5Digest(b))
  156. }
  157. }
  158. req, err = http.NewRequest(method, urlStr, reader)
  159. if err != nil {
  160. return
  161. }
  162. req.Header, err = addHeaderOptions(req.Header, optHeader)
  163. if err != nil {
  164. return
  165. }
  166. if v := req.Header.Get("Content-Length"); req.ContentLength == 0 && v != "" && v != "0" {
  167. req.ContentLength, _ = strconv.ParseInt(v, 10, 64)
  168. }
  169. if contentMD5 != "" {
  170. req.Header["Content-MD5"] = []string{contentMD5}
  171. }
  172. if v := req.Header.Get("User-Agent"); v == "" || !strings.HasPrefix(v, userAgent) {
  173. if c.UserAgent != "" {
  174. req.Header.Set("User-Agent", c.UserAgent)
  175. }
  176. }
  177. if req.Header.Get("Content-Type") == "" && contentType != "" {
  178. req.Header.Set("Content-Type", contentType)
  179. }
  180. if c.Host != "" {
  181. req.Host = c.Host
  182. }
  183. if c.Conf.RequestBodyClose {
  184. req.Close = true
  185. }
  186. return
  187. }
  188. func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{}, closeBody bool) (*Response, error) {
  189. var cancel context.CancelFunc
  190. if closeBody {
  191. ctx, cancel = context.WithCancel(ctx)
  192. defer cancel()
  193. }
  194. req = req.WithContext(ctx)
  195. resp, err := c.client.Do(req)
  196. if err != nil {
  197. // If we got an error, and the context has been canceled,
  198. // the context's error is probably more useful.
  199. select {
  200. case <-ctx.Done():
  201. return nil, ctx.Err()
  202. default:
  203. }
  204. return nil, err
  205. }
  206. defer func() {
  207. if closeBody {
  208. // Close the body to let the Transport reuse the connection
  209. io.Copy(ioutil.Discard, resp.Body)
  210. resp.Body.Close()
  211. }
  212. }()
  213. response := newResponse(resp)
  214. err = checkResponse(resp)
  215. if err != nil {
  216. // StatusCode != 2xx when Get Object
  217. if !closeBody {
  218. resp.Body.Close()
  219. }
  220. // even though there was an error, we still return the response
  221. // in case the caller wants to inspect it further
  222. return response, err
  223. }
  224. // need CRC64 verification
  225. if reader, ok := req.Body.(*teeReader); ok {
  226. if c.Conf.EnableCRC && reader.writer != nil && !reader.disableCheckSum {
  227. localcrc := reader.Crc64()
  228. scoscrc := response.Header.Get("x-cos-hash-crc64ecma")
  229. icoscrc, _ := strconv.ParseUint(scoscrc, 10, 64)
  230. if icoscrc != localcrc {
  231. return response, fmt.Errorf("verification failed, want:%v, return:%v", localcrc, icoscrc)
  232. }
  233. }
  234. }
  235. if result != nil {
  236. if w, ok := result.(io.Writer); ok {
  237. io.Copy(w, resp.Body)
  238. } else {
  239. err = xml.NewDecoder(resp.Body).Decode(result)
  240. if err == io.EOF {
  241. err = nil // ignore EOF errors caused by empty response body
  242. }
  243. }
  244. }
  245. return response, err
  246. }
  247. type sendOptions struct {
  248. // 基础 URL
  249. baseURL *url.URL
  250. // URL 中除基础 URL 外的剩余部分
  251. uri string
  252. // 请求方法
  253. method string
  254. body interface{}
  255. // url 查询参数
  256. optQuery interface{}
  257. // http header 参数
  258. optHeader interface{}
  259. // 用 result 反序列化 resp.Body
  260. result interface{}
  261. // 是否禁用自动调用 resp.Body.Close()
  262. // 自动调用 Close() 是为了能够重用连接
  263. disableCloseBody bool
  264. }
  265. func (c *Client) doRetry(ctx context.Context, opt *sendOptions) (resp *Response, err error) {
  266. if opt.body != nil {
  267. if _, ok := opt.body.(io.Reader); ok {
  268. resp, err = c.send(ctx, opt)
  269. return
  270. }
  271. }
  272. nr := 0
  273. for nr < 3 {
  274. resp, err = c.send(ctx, opt)
  275. if err != nil {
  276. if resp != nil && resp.StatusCode <= 499 {
  277. break
  278. }
  279. nr++
  280. continue
  281. }
  282. break
  283. }
  284. return
  285. }
  286. func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, err error) {
  287. req, err := c.newRequest(ctx, opt.baseURL, opt.uri, opt.method, opt.body, opt.optQuery, opt.optHeader)
  288. if err != nil {
  289. return
  290. }
  291. resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
  292. return
  293. }
  294. // addURLOptions adds the parameters in opt as URL query parameters to s. opt
  295. // must be a struct whose fields may contain "url" tags.
  296. func addURLOptions(s string, opt interface{}) (string, error) {
  297. v := reflect.ValueOf(opt)
  298. if v.Kind() == reflect.Ptr && v.IsNil() {
  299. return s, nil
  300. }
  301. u, err := url.Parse(s)
  302. if err != nil {
  303. return s, err
  304. }
  305. qs, err := query.Values(opt)
  306. if err != nil {
  307. return s, err
  308. }
  309. // 保留原有的参数,并且放在前面。因为 cos 的 url 路由是以第一个参数作为路由的
  310. // e.g. /?uploads
  311. q := u.RawQuery
  312. rq := qs.Encode()
  313. if q != "" {
  314. if rq != "" {
  315. u.RawQuery = fmt.Sprintf("%s&%s", q, qs.Encode())
  316. }
  317. } else {
  318. u.RawQuery = rq
  319. }
  320. return u.String(), nil
  321. }
  322. // addHeaderOptions adds the parameters in opt as Header fields to req. opt
  323. // must be a struct whose fields may contain "header" tags.
  324. func addHeaderOptions(header http.Header, opt interface{}) (http.Header, error) {
  325. v := reflect.ValueOf(opt)
  326. if v.Kind() == reflect.Ptr && v.IsNil() {
  327. return header, nil
  328. }
  329. h, err := httpheader.Header(opt)
  330. if err != nil {
  331. return nil, err
  332. }
  333. for key, values := range h {
  334. for _, value := range values {
  335. header.Add(key, value)
  336. }
  337. }
  338. return header, nil
  339. }
  340. // Owner defines Bucket/Object's owner
  341. type Owner struct {
  342. UIN string `xml:"uin,omitempty"`
  343. ID string `xml:",omitempty"`
  344. DisplayName string `xml:",omitempty"`
  345. }
  346. // Initiator same to the Owner struct
  347. type Initiator Owner
  348. // Response API 响应
  349. type Response struct {
  350. *http.Response
  351. }
  352. func newResponse(resp *http.Response) *Response {
  353. return &Response{
  354. Response: resp,
  355. }
  356. }
  357. // ACLHeaderOptions is the option of ACLHeader
  358. type ACLHeaderOptions struct {
  359. XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"`
  360. XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"`
  361. XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"`
  362. XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"`
  363. XCosGrantReadACP string `header:"x-cos-grant-read-acp,omitempty" url:"-" xml:"-"`
  364. XCosGrantWriteACP string `header:"x-cos-grant-write-acp,omitempty" url:"-" xml:"-"`
  365. }
  366. // ACLGrantee is the param of ACLGrant
  367. type ACLGrantee struct {
  368. Type string `xml:"type,attr"`
  369. UIN string `xml:"uin,omitempty"`
  370. URI string `xml:"URI,omitempty"`
  371. ID string `xml:",omitempty"`
  372. DisplayName string `xml:",omitempty"`
  373. SubAccount string `xml:"Subaccount,omitempty"`
  374. }
  375. // ACLGrant is the param of ACLXml
  376. type ACLGrant struct {
  377. Grantee *ACLGrantee
  378. Permission string
  379. }
  380. // ACLXml is the ACL body struct
  381. type ACLXml struct {
  382. XMLName xml.Name `xml:"AccessControlPolicy"`
  383. Owner *Owner
  384. AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
  385. }
  386. func decodeACL(resp *Response, res *ACLXml) {
  387. ItemMap := map[string]string{
  388. "ACL": "x-cos-acl",
  389. "READ": "x-cos-grant-read",
  390. "WRITE": "x-cos-grant-write",
  391. "READ_ACP": "x-cos-grant-read-acp",
  392. "WRITE_ACP": "x-cos-grant-write-acp",
  393. "FULL_CONTROL": "x-cos-grant-full-control",
  394. }
  395. publicACL := make(map[string]int)
  396. resACL := make(map[string][]string)
  397. for _, item := range res.AccessControlList {
  398. if item.Grantee == nil {
  399. continue
  400. }
  401. if item.Grantee.ID == "qcs::cam::anyone:anyone" || item.Grantee.URI == "http://cam.qcloud.com/groups/global/AllUsers" {
  402. publicACL[item.Permission] = 1
  403. } else if item.Grantee.ID != res.Owner.ID {
  404. resACL[item.Permission] = append(resACL[item.Permission], "id=\""+item.Grantee.ID+"\"")
  405. }
  406. }
  407. if publicACL["FULL_CONTROL"] == 1 || (publicACL["READ"] == 1 && publicACL["WRITE"] == 1) {
  408. resACL["ACL"] = []string{"public-read-write"}
  409. } else if publicACL["READ"] == 1 {
  410. resACL["ACL"] = []string{"public-read"}
  411. } else {
  412. resACL["ACL"] = []string{"private"}
  413. }
  414. for item, header := range ItemMap {
  415. if len(resp.Header.Get(header)) > 0 || len(resACL[item]) == 0 {
  416. continue
  417. }
  418. resp.Header.Set(header, uniqueGrantID(resACL[item]))
  419. }
  420. }
  421. func uniqueGrantID(grantIDs []string) string {
  422. res := []string{}
  423. filter := make(map[string]int)
  424. for _, id := range grantIDs {
  425. if filter[id] != 0 {
  426. continue
  427. }
  428. filter[id] = 1
  429. res = append(res, id)
  430. }
  431. return strings.Join(res, ",")
  432. }