Есть ли какая-либо клиентская библиотека Amazon S3 для Node.js, которая позволяет перечислять все файлы в ведре S3?
Самый известный aws2js и knox похоже, не имеют этой функции.
Есть ли какая-либо клиентская библиотека Amazon S3 для Node.js, которая позволяет перечислять все файлы в ведре S3?
Самый известный aws2js и knox похоже, не имеют этой функции.
Фактически aws2js поддерживает список объектов в ковше на низком уровне с помощью вызова метода s3.get()
. Для этого нужно передать параметр prefix
, который задокументирован на странице Amazon S3 REST API:
var s3 = require('aws2js').load('s3', awsAccessKeyId, awsSecretAccessKey);
s3.setBucket(bucketName);
var folder = encodeURI('some/path/to/S3/folder');
var url = '?prefix=' + folder;
s3.get(url, 'xml', function (error, data) {
console.log(error);
console.log(data);
});
Переменная data
в приведенном выше фрагменте содержит список всех объектов в ведре bucketName
.
Использование официального aws-sdk:
var allKeys = [];
function listAllKeys(marker, cb)
{
s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
allKeys.push(data.Contents);
if(data.IsTruncated)
listAllKeys(data.NextMarker, cb);
else
cb();
});
}
см. s3.listObjects
Изменить 2017:
Та же основная идея, но теперь рекомендуется listObjectsV2( ... )
и использует ContinuationToken
(см. s3.listObjectsV2):
var allKeys = [];
function listAllKeys(token, cb)
{
var opts = { Bucket: s3bucket };
if(token) opts.ContinuationToken = token;
s3.listObjectsV2(opts, function(err, data){
allKeys = allKeys.concat(data.Contents);
if(data.IsTruncated)
listAllKeys(data.NextContinuationToken, cb);
else
cb();
});
}
Здесь Node код, который я написал, чтобы собрать объекты S3 из усеченных списков.
var params = {
Bucket: <yourbucket>,
Prefix: <yourprefix>,
};
var s3DataContents = []; // Single array of all combined S3 data.Contents
function s3Print() {
if (program.al) {
// --al: Print all objects
console.log(JSON.stringify(s3DataContents, null, " "));
} else {
// --b: Print key only, otherwise also print index
var i;
for (i = 0; i < s3DataContents.length; i++) {
var head = !program.b ? (i+1) + ': ' : '';
console.log(head + s3DataContents[i].Key);
}
}
}
function s3ListObjects(params, cb) {
s3.listObjects(params, function(err, data) {
if (err) {
console.log("listS3Objects Error:", err);
} else {
var contents = data.Contents;
s3DataContents = s3DataContents.concat(contents);
if (data.IsTruncated) {
// Set Marker to last returned key
params.Marker = contents[contents.length-1].Key;
s3ListObjects(params, cb);
} else {
cb();
}
}
});
}
s3ListObjects(params, s3Print);
Обратите внимание на listObject документация NextMarker, которая НЕ всегда присутствует в возвращенном объекте данных, поэтому Я не использую его вообще в приведенном выше коде...
NextMarker - (String) Когда ответ усечен ( IsTruncatedзначение элемента в ответе истинно), вы можете использовать имя ключа в это поле в качестве маркера в последующем запросе для получения следующего набора объекты. Amazon S3 перечисляет объекты в алфавитном порядке. Примечание. элемент возвращается, только если у вас есть параметр запроса разделителя указано. Если ответ не включает NextMarker, и это усеченный, вы можете использовать значение последнего ключа в ответе как маркер в последующем запросе, чтобы получить следующий набор объектов Клавиши.
Вся программа теперь нажата на https://github.com/kenklin/s3list.
Опубликован knox-copy, когда я не смог найти хорошее существующее решение. Обертывает все детали разбиения на страницы в API Rest в знакомый поток node:
var knoxCopy = require('knox-copy');
var client = knoxCopy.createClient({
key: '<api-key-here>',
secret: '<secret-here>',
bucket: 'mrbucket'
});
client.streamKeys({
// omit the prefix to list the whole bucket
prefix: 'buckets/of/fun'
}).on('data', function(key) {
console.log(key);
});
Если вы перечисляете менее 1000 файлов, будет работать одна страница:
client.listPageOfKeys({
prefix: 'smaller/bucket/o/fun'
}, function(err, page) {
console.log(page.Contents); // <- Here your list of files
});
Meekohi дал очень хороший ответ, но (новая) документация утверждает, что NextMarker может быть неопределенным. В этом случае вы должны использовать последний ключ в качестве маркера.
Таким образом, его пример кода может быть изменен на:
var allKeys = [];
function listAllKeys(marker, cb) {
s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
allKeys.push(data.Contents);
if(data.IsTruncated)
listAllKeys(data.NextMarker || data.Contents[data.Contents.length-1].Key, cb);
else
cb();
});
}
Не могу прокомментировать исходный ответ, поскольку у меня нет необходимой репутации. Извиняюсь за плохую наценку между прочим.
Это старый вопрос, и я думаю, что AWS JS SDK сильно изменился с тех пор, как его спросили. Вот еще один способ сделать это в наши дни:
s3.listObjects({Bucket:'mybucket', Prefix:'some-pfx'}).
on('success', function handlePage(r) {
//... handle page of contents r.data.Contents
if(r.hasNextPage()) {
// There another page; handle it
r.nextPage().on('success', handlePage).send();
} else {
// Finished!
}
}).
on('error', function(r) {
// Error!
}).
send();
Если вы хотите получить список ключей только в пределах определенной папки внутри ведра S3, тогда это будет полезно.
В принципе, функция listObjects
начнет поиск из Marker
, которую мы установили, и будет искать до maxKeys: 1000
как ограничение. поэтому он будет искать по одной папке и вы получите первые 1000 ключей, которые они найдут из другой папки в ведре.
У меня есть много папок внутри моего ведра с префиксом как prod/some date/, Ex: prod/2017/05/12/ ,prod/2017/05/13/,etc
.
Я хочу получить список объектов (имена файлов) только в папке prod/2017/05/12/
, тогда я укажу prod/2017/05/12/
как мой старт и prod/2017/05/13/
[ваше следующее имя папки] как мой конец и код, который я нарушаю цикл, когда я сталкиваюсь с концом.
Каждый Key
в data.Contents
будет выглядеть следующим образом.
{ Key: 'prod/2017/05/13/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
LastModified: 2017-05-13T00:59:02.000Z,
ETag: '"630b2sdfsdfs49ef392bcc16c833004f94ae850"',
Size: 134236366,
StorageClass: 'STANDARD',
Owner: { }
}
код:
var list = [];
function listAllKeys(s3bucket, start, end) {
s3.listObjects({
Bucket: s3bucket,
Marker: start,
MaxKeys: 1000,
}, function(err, data) {
if (data.Contents) {
for (var i = 0; i < data.Contents.length; i++) {
var key = data.Contents[i].Key; //See above code for the structure of data.Contents
if (key.substring(0, 19) != end) {
list.push(key);
} else {
break; // break the loop if end arrived
}
}
console.log(list);
console.log('Total - ', list.length);
}
});
}
listAllKeys('BucketName', 'prod/2017/05/12/', 'prod/2017/05/13/');
Вывод:
[ 'prod/2017/05/12/05/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
'prod/2017/05/12/05/a36528b9-e071-4b83-a7e6-9b32d6bce6d8.jpg',
'prod/2017/05/12/05/bc4d6d4b-4455-48b3-a548-7a714c489060.jpg',
'prod/2017/05/12/05/f4b8d599-80d0-46fa-a996-e73b8fd0cd6d.jpg',
... 689 more items ]
Total - 692
В итоге я создал функцию-оболочку для ListObjectsV2, работает аналогично и принимает те же параметры, но работает рекурсивно, пока IsTruncated = false и не возвращает все ключи, найденные в виде массива во втором параметре функции обратного вызова
const AWS = require('aws-sdk')
const s3 = new AWS.S3()
function listAllKeys(params, cb)
{
var keys = []
if(params.data){
keys = keys.concat(params.data)
}
delete params['data']
s3.listObjectsV2(params, function(err, data){
if(err){
cb(err)
} else if (data.IsTruncated) {
params['ContinuationToken'] = data.NextContinuationToken
params['data'] = data.Contents
listAllKeys(params, cb)
} else {
keys = keys.concat(data.Contents)
cb(null,keys)
}
})
}
с помощью Async Generator
const { S3 } = require('aws-sdk');
const s3 = new S3();
async function* listAllKeys(opts) {
do {
const data = await s3.listObjectsV2(opts).promise();
opts.ContinuationToken = data.NextContinuationToken;
yield data;
} while (opts.ContinuationToken)
}
const opts = {
Bucket: 'bucket-xyz',
/* required */
// ContinuationToken: 'STRING_VALUE',
// Delimiter: 'STRING_VALUE',
// EncodingType: url,
// FetchOwner: true || false,
// MaxKeys: 'NUMBER_VALUE',
// Prefix: 'STRING_VALUE',
// RequestPayer: requester,
// StartAfter: 'STRING_VALUE'
};
async function main() {
// using for of await loop
for await (const data of listAllKeys(opts)) {
console.log(data.Contents)
}
// or lazy-load
const keys = listAllKeys(opts);
console.log(await keys.next());
// {value: {…}, done: false}
console.log(await keys.next());
// {value: {…}, done: false}
console.log(await keys.next());
// {value: undefined, done: true}
}
main();
// Making Observable
const lister = opts => o => {
let needMore = true;
(async () => {
const keys = listAllKeys(opts);
for await (const data of keys) {
if (data.done) break;
o.next(data);
if (!needMore) break;
}
o.complete();
})();
return () => (needMore = false);
}
// Using Rxjs
const { Observable } = require('rxjs');
const { flatMap } = require('rxjs/operators')
function listAll() {
return Observable.create(lister(opts))
.pipe(flatMap(v => v.Contents))
.subscribe(console.log);
}
listAll();
// Using Nodejs EventEmitter
const EventEmitter = require('events');
const _eve = new EventEmitter();
_eve.on('next', console.log);
const stop = lister(opts)({
next: v => _eve.emit('next', v),
error: e => _eve.emit('error', e),
complete: v => _eve.emit('complete', v)
});
Несмотря на то, что ответ @Meekohi работает технически, у меня было достаточно душевной боли в S3-части SDK SDK для NodeJS. После всех предыдущих попыток с такими модулями, как aws-sdk
, s3
, knox
, я решил установить s3cmd через Диспетчер пакетов ОС и его оболочку с помощью child_process
Что-то вроде:
var s3cmd = new cmd_exec('s3cmd', ['ls', filepath, 's3://'+inputBucket],
function (me, data) {me.stdout += data.toString();},
function (me) {me.exit = 1;}
);
response.send(s3cmd.stdout);
(Используя реализацию cmd_exec
из этого вопроса)
Этот подход работает очень хорошо - в том числе и для других проблемных вещей, таких как загрузка файлов.
При использовании нового API s3.listObjectsV2
рекурсивным решением будет:
S3Dataset.prototype.listFiles = function(params,callback) {
var self=this;
var options = {
};
for (var attrname in params) { options[attrname] = params[attrname]; }
var results=[];
var s3=self.s3Store.GetInstance();
function listAllKeys(token, callback) {
var opt={ Bucket: self._options.s3.Bucket, Prefix: self._options.s3.Key, MaxKeys: 1000 };
if(token) opt.ContinuationToken = token;
s3.listObjectsV2(opt, (error, data) => {
if (error) {
if(self.logger) this.logger.error("listFiles error:", error);
return callback(error);
} else {
for (var index in data.Contents) {
var bucket = data.Contents[index];
if(self.logger) self.logger.debug("listFiles Key: %s LastModified: %s Size: %s", bucket.Key, bucket.LastModified, bucket.Size);
if(bucket.Size>0) {
var Bucket=self._options.s3.Bucket;
var Key=bucket.Key;
var components=bucket.Key.split('/');
var name=components[components.length-1];
results.push({
name: name,
path: bucket.Key,
mtime: bucket.LastModified,
size: bucket.Size,
sizehr: formatSizeUnits(bucket.Size)
});
}
}
if( data.IsTruncated ) { // truncated page
return listAllKeys(data.NextContinuationToken, callback);
} else {
return callback(null,results);
}
}
});
}
return listAllKeys.apply(this,['',callback]);
};
где
function formatSizeUnits(bytes){
if (bytes>=1099511627776) {bytes=(bytes/1099511627776).toFixed(4)+' PB';}
else if (bytes>=1073741824) {bytes=(bytes/1073741824).toFixed(4)+' GB';}
else if (bytes>=1048576) {bytes=(bytes/1048576).toFixed(4)+' MB';}
else if (bytes>=1024) {bytes=(bytes/1024).toFixed(4)+' KB';}
else if (bytes>1) {bytes=bytes+' bytes';}
else if (bytes==1) {bytes=bytes+' byte';}
else {bytes='0 byte';}
return bytes;
}//formatSizeUnits
Самый чистый способ сделать это для меня - это выполнить s3cmd из моего node script, как это (пример: рекурсивно удалить файлы):
var exec = require('child_process').exec;
var child;
var bucket = "myBucket";
var prefix = "myPrefix"; // this parameter is optional
var command = "s3cmd del -r s3://" + bucket + "/" + prefix;
child = exec(command, {maxBuffer: 5000 * 1024}, function (error, stdout, stderr) { // the maxBuffer is here to avoid the maxBuffer node process error
console.log('stdout: ' + stdout);
if (error !== null) {
console.log('exec error: ' + error);
}
});