multipart.js 8.3 KB

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