object.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 params = this._objectRequestParams('GET', '', options);
  206. params.query = {
  207. 'list-type': 2,
  208. ...query
  209. };
  210. params.xmlResponse = true;
  211. params.successStatuses = [200];
  212. const result = await this.request(params);
  213. let objects = result.data.Contents;
  214. const that = this;
  215. if (objects) {
  216. if (!Array.isArray(objects)) {
  217. objects = [objects];
  218. }
  219. objects = objects.map(obj => ({
  220. name: obj.Key,
  221. url: that._objectUrl(obj.Key),
  222. lastModified: obj.LastModified,
  223. etag: obj.ETag,
  224. type: obj.Type,
  225. size: Number(obj.Size),
  226. storageClass: obj.StorageClass,
  227. owner: obj.Owner
  228. ? {
  229. id: obj.Owner.ID,
  230. displayName: obj.Owner.DisplayName
  231. }
  232. : null
  233. }));
  234. }
  235. let prefixes = result.data.CommonPrefixes || null;
  236. if (prefixes) {
  237. if (!Array.isArray(prefixes)) {
  238. prefixes = [prefixes];
  239. }
  240. prefixes = prefixes.map(item => item.Prefix);
  241. }
  242. return {
  243. res: result.res,
  244. objects,
  245. prefixes,
  246. isTruncated: result.data.IsTruncated === 'true',
  247. keyCount: result.data.KeyCount,
  248. continuationToken: result.data.ContinuationToken || null,
  249. nextContinuationToken: result.data.NextContinuationToken || null
  250. };
  251. };
  252. /**
  253. * Restore Object
  254. * @param {String} name the object key
  255. * @param {Object} options
  256. * @returns {{res}}
  257. */
  258. proto.restore = async function restore(name, options) {
  259. options = options || {};
  260. options.subres = Object.assign({ restore: '' }, options.subres);
  261. if (options.versionId) {
  262. options.subres.versionId = options.versionId;
  263. }
  264. const params = this._objectRequestParams('POST', name, options);
  265. params.successStatuses = [202];
  266. const result = await this.request(params);
  267. return {
  268. res: result.res
  269. };
  270. };
  271. proto._objectUrl = function _objectUrl(name) {
  272. return this._getReqUrl({ bucket: this.options.bucket, object: name });
  273. };
  274. /**
  275. * generator request params
  276. * @return {Object} params
  277. *
  278. * @api private
  279. */
  280. proto._objectRequestParams = function _objectRequestParams(method, name, options) {
  281. if (!this.options.bucket) {
  282. throw new Error('Please create a bucket first');
  283. }
  284. options = options || {};
  285. name = this._objectName(name);
  286. const params = {
  287. object: name,
  288. bucket: this.options.bucket,
  289. method,
  290. subres: options && options.subres,
  291. timeout: options && options.timeout,
  292. ctx: options && options.ctx
  293. };
  294. if (options.headers) {
  295. params.headers = {};
  296. copy(options.headers).to(params.headers);
  297. }
  298. return params;
  299. };
  300. proto._objectName = function _objectName(name) {
  301. return name.replace(/^\/+/, '');
  302. };
  303. proto._convertMetaToHeaders = function _convertMetaToHeaders(meta, headers) {
  304. if (!meta) {
  305. return;
  306. }
  307. Object.keys(meta).forEach(k => {
  308. headers[`x-oss-meta-${k}`] = meta[k];
  309. });
  310. };
  311. proto._deleteFileSafe = function _deleteFileSafe(filepath) {
  312. return new Promise(resolve => {
  313. fs.exists(filepath, exists => {
  314. if (!exists) {
  315. resolve();
  316. } else {
  317. fs.unlink(filepath, err => {
  318. if (err) {
  319. this.debug('unlink %j error: %s', filepath, err, 'error');
  320. }
  321. resolve();
  322. });
  323. }
  324. });
  325. });
  326. };