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.

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