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.

485 lines
18 KiB

  1. package cos
  2. import (
  3. "context"
  4. "encoding/xml"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "strings"
  12. "time"
  13. )
  14. // ObjectService 相关 API
  15. type ObjectService service
  16. // ObjectGetOptions is the option of GetObject
  17. type ObjectGetOptions struct {
  18. ResponseContentType string `url:"response-content-type,omitempty" header:"-"`
  19. ResponseContentLanguage string `url:"response-content-language,omitempty" header:"-"`
  20. ResponseExpires string `url:"response-expires,omitempty" header:"-"`
  21. ResponseCacheControl string `url:"response-cache-control,omitempty" header:"-"`
  22. ResponseContentDisposition string `url:"response-content-disposition,omitempty" header:"-"`
  23. ResponseContentEncoding string `url:"response-content-encoding,omitempty" header:"-"`
  24. Range string `url:"-" header:"Range,omitempty"`
  25. IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
  26. }
  27. // presignedURLTestingOptions is the opt of presigned url
  28. type presignedURLTestingOptions struct {
  29. authTime *AuthTime
  30. }
  31. // Get Object 请求可以将一个文件(Object)下载至本地。
  32. // 该操作需要对目标 Object 具有读权限或目标 Object 对所有人都开放了读权限(公有读)。
  33. //
  34. // https://www.qcloud.com/document/product/436/7753
  35. func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOptions, id ...string) (*Response, error) {
  36. var u string
  37. if len(id) == 1 {
  38. u = fmt.Sprintf("/%s?versionId=%s", encodeURIComponent(name), id[0])
  39. } else if len(id) == 0 {
  40. u = "/" + encodeURIComponent(name)
  41. } else {
  42. return nil, errors.New("wrong params")
  43. }
  44. sendOpt := sendOptions{
  45. baseURL: s.client.BaseURL.BucketURL,
  46. uri: u,
  47. method: http.MethodGet,
  48. optQuery: opt,
  49. optHeader: opt,
  50. disableCloseBody: true,
  51. }
  52. resp, err := s.client.send(ctx, &sendOpt)
  53. return resp, err
  54. }
  55. // GetToFile download the object to local file
  56. func (s *ObjectService) GetToFile(ctx context.Context, name, localpath string, opt *ObjectGetOptions, id ...string) (*Response, error) {
  57. resp, err := s.Get(ctx, name, opt, id...)
  58. if err != nil {
  59. return resp, err
  60. }
  61. defer resp.Body.Close()
  62. // If file exist, overwrite it
  63. fd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
  64. if err != nil {
  65. return resp, err
  66. }
  67. _, err = io.Copy(fd, resp.Body)
  68. fd.Close()
  69. if err != nil {
  70. return resp, err
  71. }
  72. return resp, nil
  73. }
  74. // GetPresignedURL get the object presigned to down or upload file by url
  75. func (s *ObjectService) GetPresignedURL(ctx context.Context, httpMethod, name, ak, sk string, expired time.Duration, opt interface{}) (*url.URL, error) {
  76. sendOpt := sendOptions{
  77. baseURL: s.client.BaseURL.BucketURL,
  78. uri: "/" + encodeURIComponent(name),
  79. method: httpMethod,
  80. optQuery: opt,
  81. optHeader: opt,
  82. }
  83. req, err := s.client.newRequest(ctx, sendOpt.baseURL, sendOpt.uri, sendOpt.method, sendOpt.body, sendOpt.optQuery, sendOpt.optHeader)
  84. if err != nil {
  85. return nil, err
  86. }
  87. var authTime *AuthTime
  88. if opt != nil {
  89. if opt, ok := opt.(*presignedURLTestingOptions); ok {
  90. authTime = opt.authTime
  91. }
  92. }
  93. if authTime == nil {
  94. authTime = NewAuthTime(expired)
  95. }
  96. authorization := newAuthorization(ak, sk, req, authTime)
  97. sign := encodeURIComponent(authorization)
  98. if req.URL.RawQuery == "" {
  99. req.URL.RawQuery = fmt.Sprintf("sign=%s", sign)
  100. } else {
  101. req.URL.RawQuery = fmt.Sprintf("%s&sign=%s", req.URL.RawQuery, sign)
  102. }
  103. return req.URL, nil
  104. }
  105. // ObjectPutHeaderOptions the options of header of the put object
  106. type ObjectPutHeaderOptions struct {
  107. CacheControl string `header:"Cache-Control,omitempty" url:"-"`
  108. ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
  109. ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
  110. ContentType string `header:"Content-Type,omitempty" url:"-"`
  111. ContentMD5 string `header:"Content-MD5,omitempty" url:"-"`
  112. ContentLength int `header:"Content-Length,omitempty" url:"-"`
  113. Expect string `header:"Expect,omitempty" url:"-"`
  114. Expires string `header:"Expires,omitempty" url:"-"`
  115. XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"`
  116. // 自定义的 x-cos-meta-* header
  117. XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
  118. XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-"`
  119. // 可选值: Normal, Appendable
  120. //XCosObjectType string `header:"x-cos-object-type,omitempty" url:"-"`
  121. // Enable Server Side Encryption, Only supported: AES256
  122. XCosServerSideEncryption string `header:"x-cos-server-side-encryption,omitempty" url:"-" xml:"-"`
  123. }
  124. // ObjectPutOptions the options of put object
  125. type ObjectPutOptions struct {
  126. *ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  127. *ObjectPutHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  128. }
  129. // Put Object请求可以将一个文件(Oject)上传至指定Bucket。
  130. //
  131. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
  132. //
  133. // https://www.qcloud.com/document/product/436/7749
  134. func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
  135. sendOpt := sendOptions{
  136. baseURL: s.client.BaseURL.BucketURL,
  137. uri: "/" + encodeURIComponent(name),
  138. method: http.MethodPut,
  139. body: r,
  140. optHeader: opt,
  141. }
  142. resp, err := s.client.send(ctx, &sendOpt)
  143. return resp, err
  144. }
  145. // PutFromFile put object from local file
  146. // Notice that when use this put large file need set non-body of debug req/resp, otherwise will out of memory
  147. func (s *ObjectService) PutFromFile(ctx context.Context, name string, filePath string, opt *ObjectPutOptions) (*Response, error) {
  148. fd, err := os.Open(filePath)
  149. if err != nil {
  150. return nil, err
  151. }
  152. defer fd.Close()
  153. return s.Put(ctx, name, fd, opt)
  154. }
  155. // ObjectCopyHeaderOptions is the head option of the Copy
  156. type ObjectCopyHeaderOptions struct {
  157. // When use replace directive to update meta infos
  158. CacheControl string `header:"Cache-Control,omitempty" url:"-"`
  159. ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
  160. ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
  161. ContentType string `header:"Content-Type,omitempty" url:"-"`
  162. Expires string `header:"Expires,omitempty" url:"-"`
  163. Expect string `header:"Expect,omitempty" url:"-"`
  164. XCosMetadataDirective string `header:"x-cos-metadata-directive,omitempty" url:"-" xml:"-"`
  165. XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-" xml:"-"`
  166. XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-" xml:"-"`
  167. XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-" xml:"-"`
  168. XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-" xml:"-"`
  169. XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-" xml:"-"`
  170. // 自定义的 x-cos-meta-* header
  171. XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
  172. XCosCopySource string `header:"x-cos-copy-source" url:"-" xml:"-"`
  173. XCosServerSideEncryption string `header:"x-cos-server-side-encryption,omitempty" url:"-" xml:"-"`
  174. }
  175. // ObjectCopyOptions is the option of Copy, choose header or body
  176. type ObjectCopyOptions struct {
  177. *ObjectCopyHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  178. *ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
  179. }
  180. // ObjectCopyResult is the result of Copy
  181. type ObjectCopyResult struct {
  182. XMLName xml.Name `xml:"CopyObjectResult"`
  183. ETag string `xml:"ETag,omitempty"`
  184. LastModified string `xml:"LastModified,omitempty"`
  185. }
  186. // Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G,
  187. // 超过 5G 的文件请使用分块上传 Upload - Copy。在拷贝的过程中,文件元属性和 ACL 可以被修改。
  188. //
  189. // 用户可以通过该接口实现文件移动,文件重命名,修改文件属性和创建副本。
  190. //
  191. // 注意:在跨帐号复制的时候,需要先设置被复制文件的权限为公有读,或者对目标帐号赋权,同帐号则不需要。
  192. //
  193. // https://cloud.tencent.com/document/product/436/10881
  194. func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *ObjectCopyOptions) (*ObjectCopyResult, *Response, error) {
  195. var res ObjectCopyResult
  196. if opt == nil {
  197. opt = new(ObjectCopyOptions)
  198. }
  199. if opt.ObjectCopyHeaderOptions == nil {
  200. opt.ObjectCopyHeaderOptions = new(ObjectCopyHeaderOptions)
  201. }
  202. opt.XCosCopySource = encodeURIComponent(sourceURL)
  203. sendOpt := sendOptions{
  204. baseURL: s.client.BaseURL.BucketURL,
  205. uri: "/" + encodeURIComponent(name),
  206. method: http.MethodPut,
  207. body: nil,
  208. optHeader: opt,
  209. result: &res,
  210. }
  211. resp, err := s.client.send(ctx, &sendOpt)
  212. // If the error occurs during the copy operation, the error response is embedded in the 200 OK response. This means that a 200 OK response can contain either a success or an error.
  213. if err == nil && resp.StatusCode == 200 {
  214. if res.ETag == "" {
  215. return &res, resp, errors.New("response 200 OK, but body contains an error")
  216. }
  217. }
  218. return &res, resp, err
  219. }
  220. // Delete Object请求可以将一个文件(Object)删除。
  221. //
  222. // https://www.qcloud.com/document/product/436/7743
  223. func (s *ObjectService) Delete(ctx context.Context, name string) (*Response, error) {
  224. // When use "" string might call the delete bucket interface
  225. if len(name) == 0 {
  226. return nil, errors.New("empty object name")
  227. }
  228. sendOpt := sendOptions{
  229. baseURL: s.client.BaseURL.BucketURL,
  230. uri: "/" + encodeURIComponent(name),
  231. method: http.MethodDelete,
  232. }
  233. resp, err := s.client.send(ctx, &sendOpt)
  234. return resp, err
  235. }
  236. // ObjectHeadOptions is the option of HeadObject
  237. type ObjectHeadOptions struct {
  238. IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
  239. }
  240. // Head Object请求可以取回对应Object的元数据,Head的权限与Get的权限一致
  241. //
  242. // https://www.qcloud.com/document/product/436/7745
  243. func (s *ObjectService) Head(ctx context.Context, name string, opt *ObjectHeadOptions, id ...string) (*Response, error) {
  244. var u string
  245. if len(id) == 1 {
  246. u = fmt.Sprintf("/%s?versionId=%s", encodeURIComponent(name), id[0])
  247. } else if len(id) == 0 {
  248. u = "/" + encodeURIComponent(name)
  249. } else {
  250. return nil, errors.New("wrong params")
  251. }
  252. sendOpt := sendOptions{
  253. baseURL: s.client.BaseURL.BucketURL,
  254. uri: u,
  255. method: http.MethodHead,
  256. optHeader: opt,
  257. }
  258. resp, err := s.client.send(ctx, &sendOpt)
  259. if resp != nil && resp.Header["X-Cos-Object-Type"] != nil && resp.Header["X-Cos-Object-Type"][0] == "appendable" {
  260. resp.Header.Add("x-cos-next-append-position", resp.Header["Content-Length"][0])
  261. }
  262. return resp, err
  263. }
  264. // ObjectOptionsOptions is the option of object options
  265. type ObjectOptionsOptions struct {
  266. Origin string `url:"-" header:"Origin"`
  267. AccessControlRequestMethod string `url:"-" header:"Access-Control-Request-Method"`
  268. AccessControlRequestHeaders string `url:"-" header:"Access-Control-Request-Headers,omitempty"`
  269. }
  270. // Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。
  271. //
  272. // 当CORS配置不存在时,请求返回403 Forbidden。
  273. //
  274. // https://www.qcloud.com/document/product/436/8288
  275. func (s *ObjectService) Options(ctx context.Context, name string, opt *ObjectOptionsOptions) (*Response, error) {
  276. sendOpt := sendOptions{
  277. baseURL: s.client.BaseURL.BucketURL,
  278. uri: "/" + encodeURIComponent(name),
  279. method: http.MethodOptions,
  280. optHeader: opt,
  281. }
  282. resp, err := s.client.send(ctx, &sendOpt)
  283. return resp, err
  284. }
  285. // CASJobParameters support three way: Standard(in 35 hours), Expedited(quick way, in 15 mins), Bulk(in 5-12 hours_
  286. type CASJobParameters struct {
  287. Tier string `xml:"Tier"`
  288. }
  289. // ObjectRestoreOptions is the option of object restore
  290. type ObjectRestoreOptions struct {
  291. XMLName xml.Name `xml:"RestoreRequest"`
  292. Days int `xml:"Days"`
  293. Tier *CASJobParameters `xml:"CASJobParameters"`
  294. }
  295. // PutRestore API can recover an object of type archived by COS archive.
  296. //
  297. // https://cloud.tencent.com/document/product/436/12633
  298. func (s *ObjectService) PostRestore(ctx context.Context, name string, opt *ObjectRestoreOptions) (*Response, error) {
  299. u := fmt.Sprintf("/%s?restore", encodeURIComponent(name))
  300. sendOpt := sendOptions{
  301. baseURL: s.client.BaseURL.BucketURL,
  302. uri: u,
  303. method: http.MethodPost,
  304. body: opt,
  305. }
  306. resp, err := s.client.send(ctx, &sendOpt)
  307. return resp, err
  308. }
  309. // TODO Append 接口在优化未开放使用
  310. //
  311. // Append请求可以将一个文件(Object)以分块追加的方式上传至 Bucket 中。使用Append Upload的文件必须事前被设定为Appendable。
  312. // 当Appendable的文件被执行Put Object的操作以后,文件被覆盖,属性改变为Normal。
  313. //
  314. // 文件属性可以在Head Object操作中被查询到,当您发起Head Object请求时,会返回自定义Header『x-cos-object-type』,该Header只有两个枚举值:Normal或者Appendable。
  315. //
  316. // 追加上传建议文件大小1M - 5G。如果position的值和当前Object的长度不致,COS会返回409错误。
  317. // 如果Append一个Normal的Object,COS会返回409 ObjectNotAppendable。
  318. //
  319. // Appendable的文件不可以被复制,不参与版本管理,不参与生命周期管理,不可跨区域复制。
  320. //
  321. // 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
  322. //
  323. // https://www.qcloud.com/document/product/436/7741
  324. // func (s *ObjectService) Append(ctx context.Context, name string, position int, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
  325. // u := fmt.Sprintf("/%s?append&position=%d", encodeURIComponent(name), position)
  326. // if position != 0{
  327. // opt = nil
  328. // }
  329. // sendOpt := sendOptions{
  330. // baseURL: s.client.BaseURL.BucketURL,
  331. // uri: u,
  332. // method: http.MethodPost,
  333. // optHeader: opt,
  334. // body: r,
  335. // }
  336. // resp, err := s.client.send(ctx, &sendOpt)
  337. // return resp, err
  338. // }
  339. // ObjectDeleteMultiOptions is the option of DeleteMulti
  340. type ObjectDeleteMultiOptions struct {
  341. XMLName xml.Name `xml:"Delete" header:"-"`
  342. Quiet bool `xml:"Quiet" header:"-"`
  343. Objects []Object `xml:"Object" header:"-"`
  344. //XCosSha1 string `xml:"-" header:"x-cos-sha1"`
  345. }
  346. // ObjectDeleteMultiResult is the result of DeleteMulti
  347. type ObjectDeleteMultiResult struct {
  348. XMLName xml.Name `xml:"DeleteResult"`
  349. DeletedObjects []Object `xml:"Deleted,omitempty"`
  350. Errors []struct {
  351. Key string
  352. Code string
  353. Message string
  354. } `xml:"Error,omitempty"`
  355. }
  356. // DeleteMulti 请求实现批量删除文件,最大支持单次删除1000个文件。
  357. // 对于返回结果,COS提供Verbose和Quiet两种结果模式。Verbose模式将返回每个Object的删除结果;
  358. // Quiet模式只返回报错的Object信息。
  359. // https://www.qcloud.com/document/product/436/8289
  360. func (s *ObjectService) DeleteMulti(ctx context.Context, opt *ObjectDeleteMultiOptions) (*ObjectDeleteMultiResult, *Response, error) {
  361. var res ObjectDeleteMultiResult
  362. sendOpt := sendOptions{
  363. baseURL: s.client.BaseURL.BucketURL,
  364. uri: "/?delete",
  365. method: http.MethodPost,
  366. body: opt,
  367. result: &res,
  368. }
  369. resp, err := s.client.send(ctx, &sendOpt)
  370. return &res, resp, err
  371. }
  372. // Object is the meta info of the object
  373. type Object struct {
  374. Key string `xml:",omitempty"`
  375. ETag string `xml:",omitempty"`
  376. Size int `xml:",omitempty"`
  377. PartNumber int `xml:",omitempty"`
  378. LastModified string `xml:",omitempty"`
  379. StorageClass string `xml:",omitempty"`
  380. Owner *Owner `xml:",omitempty"`
  381. }
  382. type MultiUploadOptions struct {
  383. OptIni *InitiateMultipartUploadOptions
  384. PartSize int
  385. }
  386. // MultiUpload 为高级upload接口,并发分块上传
  387. //
  388. // 需要指定分块大小 partSize >= 1 ,单位为MB
  389. // 同时请确认分块数量不超过10000
  390. //
  391. func (s *ObjectService) MultiUpload(ctx context.Context, name string, r io.Reader, opt *MultiUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
  392. optini := opt.OptIni
  393. res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
  394. if err != nil {
  395. return nil, nil, err
  396. }
  397. uploadID := res.UploadID
  398. bufSize := opt.PartSize * 1024 * 1024
  399. buffer := make([]byte, bufSize)
  400. optcom := &CompleteMultipartUploadOptions{}
  401. PartUpload := func(ch chan *Response, ctx context.Context, name string, uploadId string, partNumber int, data io.Reader, opt *ObjectUploadPartOptions) {
  402. defer func() {
  403. if err := recover(); err != nil {
  404. fmt.Println(err)
  405. }
  406. }()
  407. resp, _ := s.UploadPart(context.Background(), name, uploadId, partNumber, data, nil)
  408. ch <- resp
  409. }
  410. chs := make([]chan *Response, 10000)
  411. PartNumber := 0
  412. for i := 1; true; i++ {
  413. bytesread, err := r.Read(buffer)
  414. if err != nil {
  415. if err != io.EOF {
  416. return nil, nil, err
  417. }
  418. PartNumber = i
  419. break
  420. }
  421. chs[i] = make(chan *Response)
  422. go PartUpload(chs[i], context.Background(), name, uploadID, i, strings.NewReader(string(buffer[:bytesread])), nil)
  423. }
  424. for i := 1; i < PartNumber; i++ {
  425. resp := <-chs[i]
  426. // Notice one part fail can not get the etag according.
  427. etag := resp.Header.Get("ETag")
  428. optcom.Parts = append(optcom.Parts, Object{
  429. PartNumber: i, ETag: etag},
  430. )
  431. }
  432. v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
  433. return v, resp, err
  434. }