multipart.js 7.7 KB

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