object.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // const debug = require('debug')('ali-oss:object');
  2. const fs = require('fs');
  3. const copy = require('copy-to');
  4. const path = require('path');
  5. const mime = require('mime');
  6. const callback = require('../common/callback');
  7. const merge = require('merge-descriptors');
  8. const { isBlob } = require('../common/utils/isBlob');
  9. const { isFile } = require('../common/utils/isFile');
  10. const { isBuffer } = require('../common/utils/isBuffer');
  11. // var assert = require('assert');
  12. const proto = exports;
  13. /**
  14. * Object operations
  15. */
  16. /**
  17. * append an object from String(file path)/Buffer/ReadableStream
  18. * @param {String} name the object key
  19. * @param {Mixed} file String(file path)/Buffer/ReadableStream
  20. * @param {Object} options
  21. * @return {Object}
  22. */
  23. proto.append = async function append(name, file, options) {
  24. options = options || {};
  25. if (options.position === undefined) options.position = '0';
  26. options.subres = {
  27. append: '',
  28. position: options.position
  29. };
  30. options.method = 'POST';
  31. const result = await this.put(name, file, options);
  32. result.nextAppendPosition = result.res.headers['x-oss-next-append-position'];
  33. return result;
  34. };
  35. /**
  36. * put an object from String(file path)/Buffer/ReadableStream
  37. * @param {String} name the object key
  38. * @param {Mixed} file String(file path)/Buffer/ReadableStream
  39. * @param {Object} options
  40. * {Object} options.callback The callback parameter is composed of a JSON string encoded in Base64
  41. * {String} options.callback.url the OSS sends a callback request to this URL
  42. * {String} options.callback.host The host header value for initiating callback requests
  43. * {String} options.callback.body The value of the request body when a callback is initiated
  44. * {String} options.callback.contentType The Content-Type of the callback requests initiatiated
  45. * {Object} options.callback.customValue Custom parameters are a map of key-values, e.g:
  46. * customValue = {
  47. * key1: 'value1',
  48. * key2: 'value2'
  49. * }
  50. * @return {Object}
  51. */
  52. proto.put = async function put(name, file, options) {
  53. let content;
  54. options = options || {};
  55. name = this._objectName(name);
  56. if (isBuffer(file)) {
  57. content = file;
  58. } else if (isBlob(file) || isFile(file)) {
  59. if (!options.mime) {
  60. if (isFile(file)) {
  61. options.mime = mime.getType(path.extname(file.name));
  62. } else {
  63. options.mime = file.type;
  64. }
  65. }
  66. const stream = this._createStream(file, 0, file.size);
  67. options.contentLength = await this._getFileSize(file);
  68. try {
  69. const result = await this.putStream(name, stream, options);
  70. return result;
  71. } catch (err) {
  72. if (err.code === 'RequestTimeTooSkewed') {
  73. this.options.amendTimeSkewed = +new Date(err.serverTime) - new Date();
  74. return await this.put(name, file, options);
  75. } else {
  76. throw err;
  77. }
  78. }
  79. } else {
  80. throw new TypeError('Must provide Buffer/Blob/File for put.');
  81. }
  82. options.headers = options.headers || {};
  83. this._convertMetaToHeaders(options.meta, options.headers);
  84. const method = options.method || 'PUT';
  85. const params = this._objectRequestParams(method, name, options);
  86. callback.encodeCallback(params, options);
  87. params.mime = options.mime;
  88. params.content = content;
  89. params.successStatuses = [200];
  90. const result = await this.request(params);
  91. const ret = {
  92. name,
  93. url: this._objectUrl(name),
  94. res: result.res
  95. };
  96. if (params.headers && params.headers['x-oss-callback']) {
  97. ret.data = JSON.parse(result.data.toString());
  98. }
  99. return ret;
  100. };
  101. /**
  102. * put an object from ReadableStream. If `options.contentLength` is
  103. * not provided, chunked encoding is used.
  104. * @param {String} name the object key
  105. * @param {Readable} stream the ReadableStream
  106. * @param {Object} options
  107. * @return {Object}
  108. */
  109. proto.putStream = async function putStream(name, stream, options) {
  110. options = options || {};
  111. options.headers = options.headers || {};
  112. name = this._objectName(name);
  113. if (options.contentLength) {
  114. options.headers['Content-Length'] = options.contentLength;
  115. } else {
  116. options.headers['Transfer-Encoding'] = 'chunked';
  117. }
  118. this._convertMetaToHeaders(options.meta, options.headers);
  119. const method = options.method || 'PUT';
  120. const params = this._objectRequestParams(method, name, options);
  121. callback.encodeCallback(params, options);
  122. params.mime = options.mime;
  123. params.stream = stream;
  124. params.successStatuses = [200];
  125. const result = await this.request(params);
  126. const ret = {
  127. name,
  128. url: this._objectUrl(name),
  129. res: result.res
  130. };
  131. if (params.headers && params.headers['x-oss-callback']) {
  132. ret.data = JSON.parse(result.data.toString());
  133. }
  134. return ret;
  135. };
  136. merge(proto, require('../common/object/copyObject'));
  137. merge(proto, require('../common/object/getObjectTagging'));
  138. merge(proto, require('../common/object/putObjectTagging'));
  139. merge(proto, require('../common/object/deleteObjectTagging'));
  140. merge(proto, require('../common/image'));
  141. merge(proto, require('../common/object/getBucketVersions'));
  142. merge(proto, require('../common/object/getACL'));
  143. merge(proto, require('../common/object/putACL'));
  144. merge(proto, require('../common/object/head'));
  145. merge(proto, require('../common/object/delete'));
  146. merge(proto, require('../common/object/get'));
  147. merge(proto, require('../common/object/putSymlink'));
  148. merge(proto, require('../common/object/getSymlink'));
  149. merge(proto, require('../common/object/deleteMulti'));
  150. merge(proto, require('../common/object/getObjectMeta'));
  151. merge(proto, require('../common/object/getObjectUrl'));
  152. merge(proto, require('../common/object/generateObjectUrl'));
  153. merge(proto, require('../common/object/signatureUrl'));
  154. proto.putMeta = async function putMeta(name, meta, options) {
  155. const copyResult = await this.copy(name, name, {
  156. meta: meta || {},
  157. timeout: options && options.timeout,
  158. ctx: options && options.ctx
  159. });
  160. return copyResult;
  161. };
  162. proto.list = async function list(query, options) {
  163. // prefix, marker, max-keys, delimiter
  164. const params = this._objectRequestParams('GET', '', options);
  165. params.query = query;
  166. params.xmlResponse = true;
  167. params.successStatuses = [200];
  168. const result = await this.request(params);
  169. let objects = result.data.Contents;
  170. const that = this;
  171. if (objects) {
  172. if (!Array.isArray(objects)) {
  173. objects = [objects];
  174. }
  175. objects = objects.map(obj => ({
  176. name: obj.Key,
  177. url: that._objectUrl(obj.Key),
  178. lastModified: obj.LastModified,
  179. etag: obj.ETag,
  180. type: obj.Type,
  181. size: Number(obj.Size),
  182. storageClass: obj.StorageClass,
  183. owner: {
  184. id: obj.Owner.ID,
  185. displayName: obj.Owner.DisplayName
  186. }
  187. }));
  188. }
  189. let prefixes = result.data.CommonPrefixes || null;
  190. if (prefixes) {
  191. if (!Array.isArray(prefixes)) {
  192. prefixes = [prefixes];
  193. }
  194. prefixes = prefixes.map(item => item.Prefix);
  195. }
  196. return {
  197. res: result.res,
  198. objects,
  199. prefixes,
  200. nextMarker: result.data.NextMarker || null,
  201. isTruncated: result.data.IsTruncated === 'true'
  202. };
  203. };
  204. proto.listV2 = async function listV2(query, options = {}) {
  205. const continuation_token = query['continuation-token'] || query.continuationToken;
  206. delete query['continuation-token'];
  207. delete query.continuationToken;
  208. if (continuation_token) {
  209. options.subres = Object.assign(
  210. {
  211. 'continuation-token': continuation_token
  212. },
  213. options.subres
  214. );
  215. }
  216. const params = this._objectRequestParams('GET', '', options);
  217. params.query = Object.assign({ 'list-type': 2 }, query);
  218. params.xmlResponse = true;
  219. params.successStatuses = [200];
  220. const result = await this.request(params);
  221. let objects = result.data.Contents;
  222. const that = this;
  223. if (objects) {
  224. if (!Array.isArray(objects)) {
  225. objects = [objects];
  226. }
  227. objects = objects.map(obj => ({
  228. name: obj.Key,
  229. url: that._objectUrl(obj.Key),
  230. lastModified: obj.LastModified,
  231. etag: obj.ETag,
  232. type: obj.Type,
  233. size: Number(obj.Size),
  234. storageClass: obj.StorageClass,
  235. owner: obj.Owner
  236. ? {
  237. id: obj.Owner.ID,
  238. displayName: obj.Owner.DisplayName
  239. }
  240. : null
  241. }));
  242. }
  243. let prefixes = result.data.CommonPrefixes || null;
  244. if (prefixes) {
  245. if (!Array.isArray(prefixes)) {
  246. prefixes = [prefixes];
  247. }
  248. prefixes = prefixes.map(item => item.Prefix);
  249. }
  250. return {
  251. res: result.res,
  252. objects,
  253. prefixes,
  254. isTruncated: result.data.IsTruncated === 'true',
  255. keyCount: +result.data.KeyCount,
  256. continuationToken: result.data.ContinuationToken || null,
  257. nextContinuationToken: result.data.NextContinuationToken || null
  258. };
  259. };
  260. /**
  261. * Restore Object
  262. * @param {String} name the object key
  263. * @param {Object} options
  264. * @returns {{res}}
  265. */
  266. proto.restore = async function restore(name, options) {
  267. options = options || {};
  268. options.subres = Object.assign({ restore: '' }, options.subres);
  269. if (options.versionId) {
  270. options.subres.versionId = options.versionId;
  271. }
  272. const params = this._objectRequestParams('POST', name, options);
  273. params.successStatuses = [202];
  274. const result = await this.request(params);
  275. return {
  276. res: result.res
  277. };
  278. };
  279. proto._objectUrl = function _objectUrl(name) {
  280. return this._getReqUrl({ bucket: this.options.bucket, object: name });
  281. };
  282. /**
  283. * generator request params
  284. * @return {Object} params
  285. *
  286. * @api private
  287. */
  288. proto._objectRequestParams = function _objectRequestParams(method, name, options) {
  289. if (!this.options.bucket && !this.options.cname) {
  290. throw new Error('Please create a bucket first');
  291. }
  292. options = options || {};
  293. name = this._objectName(name);
  294. const params = {
  295. object: name,
  296. bucket: this.options.bucket,
  297. method,
  298. subres: options && options.subres,
  299. timeout: options && options.timeout,
  300. ctx: options && options.ctx
  301. };
  302. if (options.headers) {
  303. params.headers = {};
  304. copy(options.headers).to(params.headers);
  305. }
  306. return params;
  307. };
  308. proto._objectName = function _objectName(name) {
  309. return name.replace(/^\/+/, '');
  310. };
  311. proto._convertMetaToHeaders = function _convertMetaToHeaders(meta, headers) {
  312. if (!meta) {
  313. return;
  314. }
  315. Object.keys(meta).forEach(k => {
  316. headers[`x-oss-meta-${k}`] = meta[k];
  317. });
  318. };
  319. proto._deleteFileSafe = function _deleteFileSafe(filepath) {
  320. return new Promise(resolve => {
  321. fs.exists(filepath, exists => {
  322. if (!exists) {
  323. resolve();
  324. } else {
  325. fs.unlink(filepath, err => {
  326. if (err) {
  327. this.debug('unlink %j error: %s', filepath, err, 'error');
  328. }
  329. resolve();
  330. });
  331. }
  332. });
  333. });
  334. };