multipart.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. const copy = require('copy-to');
  2. const callback = require('./callback');
  3. const { deepCopy } = require('./utils/deepCopy');
  4. const proto = exports;
  5. /**
  6. * List the on-going multipart uploads
  7. * https://help.aliyun.com/document_detail/31997.html
  8. * @param {Object} options
  9. * @return {Array} the multipart uploads
  10. */
  11. proto.listUploads = async function listUploads(query, options) {
  12. options = options || {};
  13. const opt = {};
  14. copy(options).to(opt);
  15. opt.subres = 'uploads';
  16. const params = this._objectRequestParams('GET', '', opt);
  17. params.query = query;
  18. params.xmlResponse = true;
  19. params.successStatuses = [200];
  20. const result = await this.request(params);
  21. let uploads = result.data.Upload || [];
  22. if (!Array.isArray(uploads)) {
  23. uploads = [uploads];
  24. }
  25. uploads = uploads.map(up => ({
  26. name: up.Key,
  27. uploadId: up.UploadId,
  28. initiated: up.Initiated
  29. }));
  30. return {
  31. res: result.res,
  32. uploads,
  33. bucket: result.data.Bucket,
  34. nextKeyMarker: result.data.NextKeyMarker,
  35. nextUploadIdMarker: result.data.NextUploadIdMarker,
  36. isTruncated: result.data.IsTruncated === 'true'
  37. };
  38. };
  39. /**
  40. * List the done uploadPart parts
  41. * @param {String} name object name
  42. * @param {String} uploadId multipart upload id
  43. * @param {Object} query
  44. * {Number} query.max-parts The maximum part number in the response of the OSS. Default value: 1000
  45. * {Number} query.part-number-marker Starting position of a specific list.
  46. * {String} query.encoding-type Specify the encoding of the returned content and the encoding type.
  47. * @param {Object} options
  48. * @return {Object} result
  49. */
  50. proto.listParts = async function listParts(name, uploadId, query, options) {
  51. options = options || {};
  52. const opt = {};
  53. copy(options).to(opt);
  54. opt.subres = {
  55. uploadId
  56. };
  57. const params = this._objectRequestParams('GET', name, opt);
  58. params.query = query;
  59. params.xmlResponse = true;
  60. params.successStatuses = [200];
  61. const result = await this.request(params);
  62. return {
  63. res: result.res,
  64. uploadId: result.data.UploadId,
  65. bucket: result.data.Bucket,
  66. name: result.data.Key,
  67. partNumberMarker: result.data.PartNumberMarker,
  68. nextPartNumberMarker: result.data.NextPartNumberMarker,
  69. maxParts: result.data.MaxParts,
  70. isTruncated: result.data.IsTruncated,
  71. parts: result.data.Part || []
  72. };
  73. };
  74. /**
  75. * Abort a multipart upload transaction
  76. * @param {String} name the object name
  77. * @param {String} uploadId the upload id
  78. * @param {Object} options
  79. */
  80. proto.abortMultipartUpload = async function abortMultipartUpload(name, uploadId, options) {
  81. this._stop();
  82. options = options || {};
  83. const opt = {};
  84. copy(options).to(opt);
  85. opt.subres = { uploadId };
  86. const params = this._objectRequestParams('DELETE', name, opt);
  87. params.successStatuses = [204];
  88. const result = await this.request(params);
  89. return {
  90. res: result.res
  91. };
  92. };
  93. /**
  94. * Initiate a multipart upload transaction
  95. * @param {String} name the object name
  96. * @param {Object} options
  97. * @return {String} upload id
  98. */
  99. proto.initMultipartUpload = async function initMultipartUpload(name, options) {
  100. options = options || {};
  101. const opt = {};
  102. copy(options).to(opt);
  103. opt.headers = opt.headers || {};
  104. this._convertMetaToHeaders(options.meta, opt.headers);
  105. opt.subres = 'uploads';
  106. const params = this._objectRequestParams('POST', name, opt);
  107. params.mime = options.mime;
  108. params.xmlResponse = true;
  109. params.successStatuses = [200];
  110. const result = await this.request(params);
  111. return {
  112. res: result.res,
  113. bucket: result.data.Bucket,
  114. name: result.data.Key,
  115. uploadId: result.data.UploadId
  116. };
  117. };
  118. /**
  119. * Upload a part in a multipart upload transaction
  120. * @param {String} name the object name
  121. * @param {String} uploadId the upload id
  122. * @param {Integer} partNo the part number
  123. * @param {File} file upload File, whole File
  124. * @param {Integer} start part start bytes e.g: 102400
  125. * @param {Integer} end part end bytes e.g: 204800
  126. * @param {Object} options
  127. */
  128. proto.uploadPart = async function uploadPart(name, uploadId, partNo, file, start, end, options) {
  129. const data = {
  130. stream: this._createStream(file, start, end),
  131. size: end - start
  132. };
  133. return await this._uploadPart(name, uploadId, partNo, data, options);
  134. };
  135. /**
  136. * Complete a multipart upload transaction
  137. * @param {String} name the object name
  138. * @param {String} uploadId the upload id
  139. * @param {Array} parts the uploaded parts, each in the structure:
  140. * {Integer} number partNo
  141. * {String} etag part etag uploadPartCopy result.res.header.etag
  142. * @param {Object} options
  143. * {Object} options.callback The callback parameter is composed of a JSON string encoded in Base64
  144. * {String} options.callback.url the OSS sends a callback request to this URL
  145. * {String} options.callback.host The host header value for initiating callback requests
  146. * {String} options.callback.body The value of the request body when a callback is initiated
  147. * {String} options.callback.contentType The Content-Type of the callback requests initiatiated
  148. * {Object} options.callback.customValue Custom parameters are a map of key-values, e.g:
  149. * customValue = {
  150. * key1: 'value1',
  151. * key2: 'value2'
  152. * }
  153. */
  154. proto.completeMultipartUpload = async function completeMultipartUpload(name, uploadId, parts, options) {
  155. const completeParts = parts.concat().sort((a, b) => a.number - b.number)
  156. .filter((item, index, arr) => !index || item.number !== arr[index - 1].number);
  157. let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<CompleteMultipartUpload>\n';
  158. for (let i = 0; i < completeParts.length; i++) {
  159. const p = completeParts[i];
  160. xml += '<Part>\n';
  161. xml += `<PartNumber>${p.number}</PartNumber>\n`;
  162. xml += `<ETag>${p.etag}</ETag>\n`;
  163. xml += '</Part>\n';
  164. }
  165. xml += '</CompleteMultipartUpload>';
  166. options = options || {};
  167. let opt = {};
  168. opt = deepCopy(options);
  169. if (opt.headers) delete opt.headers['x-oss-server-side-encryption'];
  170. opt.subres = { uploadId };
  171. const params = this._objectRequestParams('POST', name, opt);
  172. callback.encodeCallback(params, opt);
  173. params.mime = 'xml';
  174. params.content = xml;
  175. if (!(params.headers && params.headers['x-oss-callback'])) {
  176. params.xmlResponse = true;
  177. }
  178. params.successStatuses = [200];
  179. const result = await this.request(params);
  180. const ret = {
  181. res: result.res,
  182. bucket: params.bucket,
  183. name,
  184. etag: result.res.headers.etag
  185. };
  186. if (params.headers && params.headers['x-oss-callback']) {
  187. ret.data = JSON.parse(result.data.toString());
  188. }
  189. return ret;
  190. };
  191. /**
  192. * Upload a part in a multipart upload transaction
  193. * @param {String} name the object name
  194. * @param {String} uploadId the upload id
  195. * @param {Integer} partNo the part number
  196. * @param {Object} data the body data
  197. * @param {Object} options
  198. */
  199. proto._uploadPart = async function _uploadPart(name, uploadId, partNo, data, options) {
  200. options = options || {};
  201. const opt = {};
  202. copy(options).to(opt);
  203. opt.headers = {
  204. 'Content-Length': data.size
  205. };
  206. opt.subres = {
  207. partNumber: partNo,
  208. uploadId
  209. };
  210. const params = this._objectRequestParams('PUT', name, opt);
  211. params.mime = opt.mime;
  212. params.stream = data.stream;
  213. params.successStatuses = [200];
  214. const result = await this.request(params);
  215. if (!result.res.headers.etag) {
  216. throw new Error('Please set the etag of expose-headers in OSS \n https://help.aliyun.com/document_detail/32069.html');
  217. }
  218. data.stream = null;
  219. params.stream = null;
  220. return {
  221. name,
  222. etag: result.res.headers.etag,
  223. res: result.res
  224. };
  225. };