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.

375 lines
14 KiB

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