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.

520 lines
12 KiB

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